ghostdrive1 commited on
Commit
0b9dc2e
·
verified ·
1 Parent(s): 081b75e

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gemini/styleguide.md +96 -0
  2. .gitattributes +6 -0
  3. .gitignore +161 -0
  4. .pre-commit-config.yaml +116 -0
  5. CONTRIBUTING.md +420 -0
  6. CONTRIBUTING_zh.md +298 -0
  7. Dockerfile +25 -0
  8. LICENSE +391 -0
  9. README.md +274 -10
  10. README_zh.md +297 -0
  11. assets/images/agentscope.png +3 -0
  12. assets/images/bg_tool.gif +3 -0
  13. assets/images/dingtalk_qr_code.png +0 -0
  14. assets/images/permission_bypass.gif +3 -0
  15. assets/images/task.gif +3 -0
  16. assets/images/team.gif +3 -0
  17. docs/NEWS.md +10 -0
  18. docs/NEWS_zh.md +10 -0
  19. docs/changelog.md +98 -0
  20. docs/roadmap.md +121 -0
  21. examples/agent_service/README.md +78 -0
  22. examples/agent_service/main.py +131 -0
  23. examples/long_term_memory/agentic_memory/README.md +83 -0
  24. examples/long_term_memory/agentic_memory/main.py +238 -0
  25. examples/long_term_memory/mem0/README.md +387 -0
  26. examples/long_term_memory/mem0/oss_demo.py +325 -0
  27. examples/rag/README.md +36 -0
  28. examples/rag/index_and_search.py +183 -0
  29. examples/rag/integrate_with_agent.py +198 -0
  30. examples/web_ui/.gitignore +42 -0
  31. examples/web_ui/.husky/pre-commit +1 -0
  32. examples/web_ui/.prettierignore +38 -0
  33. examples/web_ui/.prettierrc +10 -0
  34. examples/web_ui/backend/package.json +22 -0
  35. examples/web_ui/backend/src/index.ts +16 -0
  36. examples/web_ui/backend/tsconfig.json +16 -0
  37. examples/web_ui/frontend/README.md +73 -0
  38. examples/web_ui/frontend/components.json +25 -0
  39. examples/web_ui/frontend/eslint.config.js +53 -0
  40. examples/web_ui/frontend/index.html +13 -0
  41. examples/web_ui/frontend/package.json +66 -0
  42. examples/web_ui/frontend/public/agentscope.svg +1 -0
  43. examples/web_ui/frontend/src/App.tsx +84 -0
  44. examples/web_ui/frontend/src/api/agent.ts +22 -0
  45. examples/web_ui/frontend/src/api/chat.ts +25 -0
  46. examples/web_ui/frontend/src/api/client.ts +111 -0
  47. examples/web_ui/frontend/src/api/credential.ts +23 -0
  48. examples/web_ui/frontend/src/api/index.ts +9 -0
  49. examples/web_ui/frontend/src/api/knowledgeBase.ts +179 -0
  50. examples/web_ui/frontend/src/api/model.ts +10 -0
.gemini/styleguide.md ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AgentScope Code Review Guide
2
+
3
+ You should conduct a strict code review. Each requirement is labeled with priority:
4
+ - **[MUST]** must be satisfied or PR will be rejected
5
+ - **[SHOULD]** strongly recommended
6
+ - **[MAY]** optional suggestion
7
+
8
+ ## 1. Code Quality
9
+
10
+ ### [MUST] Lazy Loading
11
+ - Third-party library dependencies should be imported at the point of use, avoid centralized imports at file top
12
+ - The `Third-party library` refers to libraries not included in the `dependencies` variable in `pyproject.toml`.
13
+ - For base class imports, use factory pattern:
14
+ ```python
15
+ def get_xxx_cls() -> "MyClass":
16
+ from xxx import BaseClass
17
+ class MyClass(BaseClass): ...
18
+ return MyClass
19
+ ```
20
+
21
+ ### [SHOULD] Code Conciseness
22
+ After understanding the code intent, check if it can be optimized:
23
+ - Avoid unnecessary temporary variables
24
+ - Merge duplicate code blocks
25
+ - Prioritize reusing existing utility functions
26
+
27
+ ### [MUST] Encapsulation Standards
28
+ - All Python files under `src/agentscope` should be named with `_` prefix, and exposure controlled through `__init__.py`
29
+ - Classes and functions used internally by the framework that don't need to be exposed to users must be named with `_` prefix
30
+
31
+ ## 2. [MUST] Code Security
32
+ - Prohibit hardcoding API keys/tokens/passwords
33
+ - Use environment variables or configuration files for management
34
+ - Check for debug information and temporary credentials
35
+ - Check for injection attack risks (SQL/command/code injection, etc.)
36
+
37
+ ## 3. [MUST] Testing & Dependencies
38
+ - New features must include unit tests
39
+ - New dependencies need to be added to the corresponding section in `pyproject.toml`
40
+ - Dependencies for non-core scenarios should not be added to the minimal dependency list
41
+
42
+ ## 4. Code Standards
43
+
44
+ ### [MUST] Comment Standards
45
+ - **Use English**
46
+ - All classes/methods must have complete docstrings, strictly following the template:
47
+ ```python
48
+ def func(a: str, b: int | None = None) -> str:
49
+ """{description}
50
+
51
+ Args:
52
+ a (`str`):
53
+ The argument a
54
+ b (`int | None`, optional):
55
+ The argument b
56
+
57
+ Returns:
58
+ `str`:
59
+ The return str
60
+ """
61
+ ```
62
+ - Use reStructuredText syntax for special content:
63
+ ```python
64
+ class MyClass:
65
+ """xxx
66
+
67
+ `Example link <https://xxx>`_
68
+
69
+ .. note:: Example note
70
+
71
+ .. tip:: Example tip
72
+
73
+ .. important:: Example important info
74
+
75
+ .. code-block:: python
76
+
77
+ def hello_world():
78
+ print("Hello world!")
79
+
80
+ """
81
+ ```
82
+
83
+ ### [MUST] Pre-commit Checks
84
+ - **Strict review**: In most cases, code should be modified rather than skipping checks
85
+ - **File-level check skipping is prohibited**
86
+ - Only allowed skip: agent class system prompt parameters (to avoid `\n` formatting issues)
87
+
88
+ ---
89
+
90
+ ## 5. Git Standards
91
+
92
+ ### [MUST] PR Title
93
+ - Follow Conventional Commits
94
+ - Must use prefixes: `feat/fix/docs/ci/refactor/test`, etc.
95
+ - Format: `feat(scope): description`
96
+ - Example: `feat(memory): add redis cache support`
.gitattributes CHANGED
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/images/agentscope.png filter=lfs diff=lfs merge=lfs -text
37
+ assets/images/bg_tool.gif filter=lfs diff=lfs merge=lfs -text
38
+ assets/images/permission_bypass.gif filter=lfs diff=lfs merge=lfs -text
39
+ assets/images/task.gif filter=lfs diff=lfs merge=lfs -text
40
+ assets/images/team.gif filter=lfs diff=lfs merge=lfs -text
41
+ scripts/model_examples/test.jpeg filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ .python-version
86
+
87
+ # pipenv
88
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
90
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
91
+ # install all needed dependencies.
92
+ #Pipfile.lock
93
+
94
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95
+ __pypackages__/
96
+
97
+ # Celery stuff
98
+ celerybeat-schedule
99
+ celerybeat.pid
100
+
101
+ # SageMath parsed files
102
+ *.sage.py
103
+
104
+ # Environments
105
+ .env
106
+ .venv
107
+ env/
108
+ venv/
109
+ ENV/
110
+ env.bak/
111
+ venv.bak/
112
+
113
+ # Spyder project settings
114
+ .spyderproject
115
+ .spyproject
116
+
117
+ # Rope project settings
118
+ .ropeproject
119
+
120
+ # mkdocs documentation
121
+ /site
122
+
123
+ # mypy
124
+ .mypy_cache/
125
+ .dmypy.json
126
+ dmypy.json
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+
131
+ .idea/
132
+
133
+ # macOS
134
+ .DS_Store
135
+
136
+ # docs
137
+ docs/tutorial/en/build/
138
+ docs/tutorial/zh_CN/build/
139
+
140
+ # Sphinx build artifacts
141
+ docs/tutorial/**/doctrees/
142
+ docs/tutorial/**/.doctrees/
143
+ *.buildinfo
144
+ *.pickle
145
+
146
+ node_modules/
147
+ package-lock.json
148
+ *.tsbuildinfo
149
+ .wireit/
150
+ .angular/
151
+ uv.lock
152
+
153
+ # JS/Node frontend (examples/web_ui)
154
+ *.local
155
+ .eslintcache
156
+ .vite/
157
+ npm-debug.log*
158
+ pnpm-debug.log*
159
+ yarn-debug.log*
160
+ yarn-error.log*
161
+ lerna-debug.log*
.pre-commit-config.yaml ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.3.0
4
+ hooks:
5
+ - id: check-ast
6
+ - id: sort-simple-yaml
7
+ - id: check-yaml
8
+ exclude: |
9
+ (?x)^(
10
+ meta.yaml
11
+ )$
12
+ - id: check-xml
13
+ - id: check-toml
14
+ - id: check-docstring-first
15
+ - id: check-json
16
+ exclude: |
17
+ (?x)^(
18
+ .*tsconfig(\.[^.]+)?\.json
19
+ )$
20
+ - id: fix-encoding-pragma
21
+ - id: detect-private-key
22
+ - id: trailing-whitespace
23
+ - repo: https://github.com/asottile/add-trailing-comma
24
+ rev: v3.1.0
25
+ hooks:
26
+ - id: add-trailing-comma
27
+ - repo: https://github.com/pre-commit/mirrors-mypy
28
+ rev: v1.7.0
29
+ hooks:
30
+ - id: mypy
31
+ exclude:
32
+ (?x)(
33
+ pb2\.py$
34
+ | grpc\.py$
35
+ | ^docs
36
+ | \.html$
37
+ )
38
+ args: [ --disallow-untyped-defs,
39
+ --disallow-incomplete-defs,
40
+ --ignore-missing-imports,
41
+ --disable-error-code=var-annotated,
42
+ --disable-error-code=union-attr,
43
+ --disable-error-code=assignment,
44
+ --disable-error-code=attr-defined,
45
+ --disable-error-code=import-untyped,
46
+ --disable-error-code=truthy-function,
47
+ --disable-error-code=typeddict-item,
48
+ --follow-imports=skip,
49
+ --explicit-package-bases,
50
+ ]
51
+ # - repo: https://github.com/numpy/numpydoc
52
+ # rev: v1.6.0
53
+ # hooks:
54
+ # - id: numpydoc-validation
55
+ - repo: https://github.com/psf/black
56
+ rev: 23.3.0
57
+ hooks:
58
+ - id: black
59
+ args: [--line-length=79]
60
+ - repo: https://github.com/PyCQA/flake8
61
+ rev: 6.1.0
62
+ hooks:
63
+ - id: flake8
64
+ args: ["--extend-ignore=E203"]
65
+ exclude: ^docs
66
+ - repo: https://github.com/pylint-dev/pylint
67
+ rev: v3.0.2
68
+ hooks:
69
+ - id: pylint
70
+ exclude:
71
+ (?x)(
72
+ ^docs
73
+ | pb2\.py$
74
+ | grpc\.py$
75
+ | \.demo$
76
+ | \.md$
77
+ | \.html$
78
+ | ^examples/paper_llm_based_algorithm/
79
+ )
80
+ args: [
81
+ --disable=W0511,
82
+ --disable=W0718,
83
+ --disable=W0122,
84
+ --disable=C0103,
85
+ --disable=R0913,
86
+ --disable=E0401,
87
+ --disable=E1101,
88
+ --disable=C0415,
89
+ --disable=W0603,
90
+ --disable=R1705,
91
+ --disable=R0914,
92
+ --disable=E0601,
93
+ --disable=W0602,
94
+ --disable=W0604,
95
+ --disable=R0801,
96
+ --disable=R0902,
97
+ --disable=R0903,
98
+ --disable=C0123,
99
+ --disable=W0231,
100
+ --disable=W1113,
101
+ --disable=W0221,
102
+ --disable=R0401,
103
+ --disable=W0632,
104
+ --disable=W0123,
105
+ --disable=C3001,
106
+ --max-branches=30,
107
+ --max-nested-blocks=7,
108
+ --max-statements=100,
109
+ --max-returns=10,
110
+ --max-module-lines=3000,
111
+ ]
112
+ - repo: https://github.com/regebro/pyroma
113
+ rev: "5.0"
114
+ hooks:
115
+ - id: pyroma
116
+ args: [--min=10, .]
CONTRIBUTING.md ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to AgentScope
2
+
3
+
4
+ Thank you for your interest in contributing to AgentScope!
5
+
6
+ As an open-source project, we warmly welcome and encourage
7
+ contributions from the community. Whether you're fixing bugs, adding new features, improving documentation, or sharing
8
+ ideas, your contributions help make AgentScope better for everyone.
9
+
10
+ ## 1. Development Roadmap and How to Get Involved
11
+
12
+ To support the long-term, healthy growth of AgentScope and its open-source
13
+ community, we keep our development plan transparent and openly tracked.
14
+
15
+ **Our roadmap is public.** The AgentScope development plan is published and
16
+ continuously updated on our [GitHub Projects page](https://github.com/orgs/agentscope-ai/projects/2).
17
+ The roadmap reflects the technical direction set by the core team, who are
18
+ responsible for AgentScope's overall design and quality.
19
+
20
+ **Tasks open to the community.** Items labeled `help wanted` on the Projects
21
+ page/issues are contribution opportunities open to everyone. If one of these
22
+ interests you:
23
+
24
+ - Comment on the related issue to let us know you'd like to take it on
25
+ - This helps us avoid duplicate efforts and coordinate with you early
26
+
27
+ **If you'd like to join the core development.** We warmly welcome contributors
28
+ who want to go deeper and help shape AgentScope itself. Over time, we plan to
29
+ gradually invite committed contributors into the core development circle.
30
+ Before reaching out, we'd like to share a few honest expectations so you can
31
+ decide whether it's a good fit right now:
32
+
33
+ - Core development involves frequent design discussions, code reviews, and
34
+ iterative revisions — it asks for a sustained investment of time and energy
35
+ - To keep AgentScope cohesive and reliable, the core team retains
36
+ responsibility for the project's technical direction and quality bar; core
37
+ contributors work within this collaborative process
38
+
39
+ If this fits your situation, please reach out to the core developers — we'd
40
+ love to talk.
41
+
42
+ **Proposing something new.** If you have an idea that isn't on the roadmap
43
+ yet, please open a new issue describing your proposal. The core team will
44
+ respond and discuss it with you so we can find the best path forward together.
45
+
46
+ ## 2. Responsible Use of AI in Contributions
47
+
48
+ AgentScope welcomes contributors who use AI coding assistants — Claude Code,
49
+ Cursor, Codex, Copilot, and others. We just ask that they be used
50
+ **responsibly**. AgentScope is sustained by reviewer time and community
51
+ trust, and AI-assisted contributions need to honor both.
52
+
53
+ A few expectations when AI is involved in your work:
54
+
55
+ - **You — not the AI — are the author.** Read the diff line by line, run it,
56
+ and make sure you understand *what* changed and *why* before you push.
57
+ "Claude Code / Cursor / Codex told me to do it" is not an acceptable
58
+ answer in code review, and is not the kind of behavior that builds a
59
+ healthy open-source community. PRs whose authors cannot explain their own
60
+ changes will be closed.
61
+
62
+ - **Review your AI-generated code before opening a PR.** Reviewer time is
63
+ the most precious resource in this project. Don't outsource your own
64
+ review to the maintainers by dumping unreviewed AI output into a PR.
65
+
66
+ - **Keep PRs atomic.** Do not submit a 10K+-line PR produced by an AI in a
67
+ single shot. Such PRs are unreviewable and will be rejected. Break the
68
+ work into focused, single-purpose PRs the same way a human contributor
69
+ would.
70
+
71
+ - **AI-assisted code follows the same rules.** All of AgentScope's
72
+ development principles — modularity, lazy imports, conventional commits,
73
+ test coverage, no surprise API breaks — apply identically to code written
74
+ with AI assistance. AI is not an excuse for skipping conventions.
75
+
76
+ The goal is simple: AI helps you move faster, but the responsibility for
77
+ what lands in AgentScope still rests with you as a human contributor.
78
+
79
+ ## 3. Contribution Workflow
80
+
81
+ End-to-end, contributing a change to AgentScope looks like this.
82
+
83
+ ### Step 1. Claim or create an issue
84
+
85
+ Before writing code, find or open the issue that frames your work.
86
+
87
+ - **Working on an existing item?** Browse [Projects](https://github.com/orgs/agentscope-ai/projects/2)
88
+ and [Issues](https://github.com/agentscope-ai/agentscope/issues) for items
89
+ labeled `help wanted` (see [§1](#1-development-roadmap-and-how-to-get-involved)).
90
+ Comment on the issue to claim it before starting.
91
+ - **Proposing something new?** Open a new issue describing the problem,
92
+ your proposed solution, and any design alternatives. Wait for feedback
93
+ from the core team before starting a non-trivial implementation — this
94
+ avoids wasted rewrites.
95
+
96
+ ### Step 2. Fork the repo and create a development branch
97
+
98
+ 1. Fork [agentscope-ai/agentscope](https://github.com/agentscope-ai/agentscope) on GitHub.
99
+ 2. Clone your fork and add the upstream remote:
100
+ ```bash
101
+ git clone https://github.com/<your-username>/agentscope.git
102
+ cd agentscope
103
+ git remote add upstream https://github.com/agentscope-ai/agentscope.git
104
+ ```
105
+ 3. Create a topic branch off the latest `main`:
106
+ ```bash
107
+ git checkout main
108
+ git pull upstream main
109
+ git checkout -b feat/<short-description>
110
+ ```
111
+ Use a branch name aligned with the change type, e.g., `feat/redis-memory`,
112
+ `fix/react-agent-leak`, `docs/contributing-update`.
113
+
114
+ ### Step 3. Set up your local environment
115
+
116
+ AgentScope requires **Python 3.11+** (see `pyproject.toml`).
117
+
118
+ ```bash
119
+ # Create an isolated environment (uv shown; virtualenv / conda also fine)
120
+ uv venv
121
+ source .venv/bin/activate
122
+
123
+ # Install AgentScope in editable mode with the dev extras
124
+ pip install -e ".[dev]"
125
+ # or, equivalently, with uv:
126
+ uv pip install -e ".[dev]"
127
+
128
+ # Enable the git pre-commit hooks
129
+ pre-commit install
130
+ ```
131
+
132
+ The `dev` extra pulls in `pre-commit`, `pytest`, the documentation
133
+ toolchain, and the `full` extra (which itself includes `models`, `service`,
134
+ and `storage`). A single installation gives you everything needed to develop
135
+ and run the complete test suite.
136
+
137
+ ### Step 4. Develop
138
+
139
+ A few conventions to follow while writing code:
140
+
141
+ - **Lazy imports for optional dependencies.** Any dependency **not listed in
142
+ `[project.dependencies]` of `pyproject.toml`** — i.e., anything coming
143
+ from the optional groups (`gemini`, `ollama`, `xai`, `service`, `storage`,
144
+ etc.) — **must be lazy-imported** at point of use rather than at module
145
+ top level:
146
+ ```python
147
+ def some_function():
148
+ import google.genai # from the `gemini` extra — lazy-imported
149
+ # ... use google.genai here
150
+ ```
151
+ This keeps `import agentscope` lightweight, and `ImportError` surfaces
152
+ only when a feature actually relying on the extra is invoked. If your
153
+ change requires a brand-new dependency, decide first whether it belongs
154
+ in the base `[project.dependencies]` (always required, kept small) or in
155
+ one of the optional extras — and discuss it in the issue before merging.
156
+
157
+ - **Follow the project's code style.** Pre-commit handles formatting and
158
+ most lint rules automatically. Don't fight the formatter.
159
+
160
+ - **Write unit tests alongside features.** Tests live under `tests/` and
161
+ follow the existing structure. Tests that rely on an optional extra
162
+ (e.g., Redis, Ollama) should skip cleanly when that extra isn't
163
+ installed.
164
+
165
+ ### Step 5. Run pre-commit, tests, and update documentation
166
+
167
+ Before opening the PR, run the same checks CI will run:
168
+
169
+ ```bash
170
+ # Auto-format and lint
171
+ pre-commit run --all-files
172
+
173
+ # Run the unit tests
174
+ pytest tests
175
+ ```
176
+
177
+ If a pre-commit hook fails, fix the issue (most fixes are applied
178
+ automatically) and re-stage the files. Don't bypass hooks with
179
+ `--no-verify`.
180
+
181
+ **Update documentation alongside the code change.**
182
+
183
+ - AgentScope's user-facing documentation lives in a separate repository:
184
+ **[agentscope-ai/docs](https://github.com/agentscope-ai/docs)**. If your
185
+ change affects user-facing behavior — new modules, new public APIs,
186
+ behavior changes, tutorials — please open a companion PR there.
187
+ - Update inline docstrings and example snippets for any new public APIs.
188
+ - Update `README.md` if your change affects how users get started or what
189
+ AgentScope advertises.
190
+
191
+ ### Step 6. Commit and open a pull request
192
+
193
+ **Commit message format.** We follow the [Conventional Commits](https://www.conventionalcommits.org/)
194
+ specification. This keeps commit history readable and enables automatic
195
+ changelog generation.
196
+
197
+ ```
198
+ <type>(<scope>): <subject>
199
+ ```
200
+
201
+ **Types:**
202
+ - `feat:` A new feature
203
+ - `fix:` A bug fix
204
+ - `docs:` Documentation only changes
205
+ - `style:` Changes that do not affect the meaning of the code (whitespace, formatting, etc.)
206
+ - `refactor:` A code change that neither fixes a bug nor adds a feature
207
+ - `perf:` A code change that improves performance
208
+ - `ci:` Adding missing tests or correcting existing tests
209
+ - `chore:` Changes to the build process or auxiliary tools and libraries
210
+
211
+ **Examples:**
212
+ ```bash
213
+ feat(models): add support for Claude-3 model
214
+ fix(agent): resolve memory leak in ReActAgent
215
+ docs(readme): update installation instructions
216
+ refactor(formatter): simplify message formatting logic
217
+ ci(models): add unit tests for OpenAI integration
218
+ ```
219
+
220
+ **Pull request title format.** PR titles follow the same Conventional
221
+ Commits format and are validated automatically by GitHub Actions on PRs
222
+ against `main`. PRs with invalid titles will be blocked until corrected.
223
+
224
+ ```
225
+ <type>(<scope>): <description>
226
+ ```
227
+
228
+ **Requirements:**
229
+ - Title must start with one of: `feat`, `fix`, `docs`, `ci`, `refactor`, `test`, `chore`, `perf`, `style`, `build`, `revert`
230
+ - Scope is optional but recommended
231
+ - **Scope must be lowercase** — only lowercase letters, numbers, hyphens (`-`), and underscores (`_`) are allowed
232
+ - Description should start with a lowercase letter
233
+ - Keep the title concise and descriptive
234
+
235
+ **Examples:**
236
+ ```
237
+ ✅ Valid:
238
+ feat(memory): add redis cache support
239
+ fix(agent): resolve memory leak in ReActAgent
240
+ docs(tutorial): update installation guide
241
+ ci(workflow): add PR title validation
242
+ refactor(my-feature): simplify logic
243
+
244
+ ❌ Invalid:
245
+ feat(Memory): add cache # Scope must be lowercase
246
+ feat(MEMORY): add cache # Scope must be lowercase
247
+ feat(MyFeature): add feature # Scope must be lowercase
248
+ ```
249
+
250
+ **Open the PR.** Push your branch to your fork and open a pull request
251
+ against `agentscope-ai/agentscope:main`. In the PR description:
252
+
253
+ - Link the issue you claimed (`Fixes #123` or `Refs #123`)
254
+ - Summarize what changed and why
255
+ - Note any breaking changes, deprecations, or migration steps
256
+ - Link the companion docs PR in [agentscope-ai/docs](https://github.com/agentscope-ai/docs)
257
+ if you opened one
258
+
259
+ ## 4. Important Notices
260
+
261
+ A few cross-cutting constraints worth knowing before you start a
262
+ contribution. Module-specific notices live in the corresponding module
263
+ guide below.
264
+
265
+ - **Open an issue before non-trivial work.** Surprise PRs that touch many
266
+ files, change public APIs, or introduce a new module are difficult to
267
+ review and likely to be rejected. Discuss the design in an issue first.
268
+ - **Keep PRs focused and atomic.** One PR, one purpose. Don't bundle a
269
+ refactor with a feature, or a feature with an unrelated bug fix.
270
+ - **Don't break public APIs without notice.** Maintain backward
271
+ compatibility when you can. If a breaking change is unavoidable, call it
272
+ out clearly in the PR description and update the affected examples and
273
+ docs in the same PR.
274
+ - **Don't bypass the lazy import principle.** Optional dependencies must be
275
+ imported at point of use, not at module top level.
276
+ - **Don't add dependencies casually.** Every new dependency is a long-term
277
+ maintenance commitment. If a dependency is needed by only one module,
278
+ prefer a lazy import inside that module.
279
+ - **Don't ignore CI failures.** Pre-commit, type checks, and tests must
280
+ pass before a PR is ready for review. Don't push the burden of fixing
281
+ them onto the reviewer.
282
+ - **Be respectful.** Follow our Code of Conduct. AgentScope's review
283
+ culture is direct but kind, and we expect the same from contributors.
284
+
285
+ ## 5. Module-Specific Contribution Guides
286
+
287
+ The notes below cover the modules most commonly extended by community
288
+ contributors. For other modules, please open an issue first so we can
289
+ coordinate.
290
+
291
+ ### Chat Model
292
+
293
+ A chat model in AgentScope is more than a single class — to be usable
294
+ inside an `Agent`, it needs a small set of upstream/downstream pieces.
295
+ A complete chat-model contribution includes **all** of the following:
296
+
297
+ 1. **Credential class** — under `agentscope.credential`, subclassing
298
+ `CredentialBase`. Carries the API key, endpoint, and other auth fields
299
+ your SDK needs.
300
+ _Reference: `agentscope/credential/_anthropic.py`_
301
+
302
+ 2. **Chat model class** — under `agentscope.model.<provider>/`, subclassing
303
+ `ChatModelBase`. The implementation needs to cover:
304
+ - Both streaming and non-streaming modes
305
+ - Tools API integration (function/tool calling)
306
+ - The `tool_choice` argument
307
+ - Reasoning models, where applicable
308
+
309
+ _Reference: `agentscope/model/_anthropic/`_
310
+
311
+ 3. **Model card YAML(s)** — under
312
+ `agentscope.model.<provider>._models/`, one YAML per supported model.
313
+ Required fields: `name`, `label`, `status`, `input_types`,
314
+ `output_types`, `context_size`, `output_size`. Optional:
315
+ `parameter_overrides`, `deprecated_at`.
316
+
317
+ Example (`claude-sonnet-4-6.yaml`):
318
+ ```yaml
319
+ name: claude-sonnet-4-6
320
+ label: Claude Sonnet 4.6
321
+ status: active
322
+ input_types:
323
+ - text/plain
324
+ - image/jpeg
325
+ output_types:
326
+ - text/plain
327
+ context_size: 1000000
328
+ output_size: 65536
329
+ parameter_overrides:
330
+ max_tokens: {"maximum": 65536}
331
+ ```
332
+
333
+ 4. **Formatter classes** — under `agentscope.formatter`, both subclassing
334
+ `FormatterBase`. Two variants are required because some APIs treat
335
+ multi-agent conversations differently from single-user chat:
336
+ - `<Provider>ChatFormatter` for single-user chat scenarios
337
+ - `<Provider>MultiAgentFormatter` for multi-agent scenarios
338
+
339
+ Each formatter converts `Msg` objects into the request format the
340
+ provider's API expects.
341
+ _Reference: `agentscope/formatter/_anthropic_formatter.py`_
342
+
343
+ > ⚠️ PRs that add only the model class without the matching credential,
344
+ > model card YAML, and both formatter variants will not be merged.
345
+
346
+ ### Agent
347
+
348
+ AgentScope deliberately maintains a **single core agent class** —
349
+ `agentscope.agent.Agent` — that integrates all functionality of the
350
+ AgentScope library (memory, tools, MCP, formatters, models, etc.).
351
+
352
+ For specialized or domain-specific agents, please contribute them as
353
+ [examples](#examples) rather than as new classes in `agentscope.agent`.
354
+
355
+ If you believe a use case genuinely requires a new top-level agent class:
356
+
357
+ 1. **Open an issue first** describing the use case and explaining why
358
+ composing existing `Agent` capabilities is insufficient.
359
+ 2. **Wait for design discussion** with the core team before starting any
360
+ implementation.
361
+ 3. PRs that introduce a new agent class without prior discussion will be
362
+ rejected.
363
+
364
+ ### Workspace
365
+
366
+ A Workspace provides the runtime context an agent operates in (skills,
367
+ scheduled tasks, etc.). Adding a new workspace backend requires two
368
+ classes plus documentation:
369
+
370
+ 1. **Workspace class** — under `agentscope.workspace`, subclassing
371
+ `WorkspaceBase`. Implements the storage and lifecycle semantics of
372
+ your backend.
373
+ _Reference: `agentscope/workspace/_local_workspace.py` (`LocalWorkspace`)_
374
+
375
+ 2. **Workspace manager class** — alongside
376
+ `agentscope/app/_manager/_workspace_manager.py`, subclassing
377
+ `WorkspaceManagerBase`. Wires your workspace into the application
378
+ lifecycle.
379
+ _Reference: `LocalWorkspaceManager` in the same file._
380
+
381
+ 3. **Documentation** — open a companion PR in
382
+ [agentscope-ai/docs](https://github.com/agentscope-ai/docs) describing
383
+ how to configure and use your workspace.
384
+
385
+ ### Examples
386
+
387
+ We highly encourage contributions of new examples that showcase
388
+ AgentScope's capabilities.
389
+
390
+ The `examples/` directory in the main repository focuses on
391
+ **demonstrating specific features and capabilities** — concise,
392
+ educational reference implementations. For more complete, production-style
393
+ applications, please contribute them to
394
+ **[agentscope-samples](https://github.com/agentscope-ai/agentscope-samples)**
395
+ instead.
396
+
397
+ A new example should live in its own subdirectory:
398
+
399
+ ```
400
+ examples/
401
+ └── <example-name>/
402
+ ├── main.py
403
+ ├── README.md # explain the example's purpose, how to run it, and expected output
404
+ └── ...
405
+ ```
406
+
407
+ `examples/agent_service/` is a good starting reference.
408
+
409
+ ## Getting Help
410
+
411
+ If you need assistance or have questions:
412
+
413
+ - Open a [Discussion](https://github.com/agentscope-ai/agentscope/discussions)
414
+ - Report bugs via [Issues](https://github.com/agentscope-ai/agentscope/issues)
415
+ - Contact the maintainers at DingTalk or Discord (links in the README.md)
416
+
417
+
418
+ ---
419
+
420
+ Thank you for contributing to AgentScope! Your efforts help build a better tool for the entire community.
CONTRIBUTING_zh.md ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 为 AgentScope 做贡献
2
+
3
+ 感谢大家对 AgentScope 的关注!
4
+
5
+ 作为一个开源项目,我们欢迎并鼓励来自社区的贡献。无论是修复 bug、新增功能、完善文档,还是分享想法,每一份贡献都让 AgentScope 变得更好。
6
+
7
+ ## 1. 开发路线图与参与方式
8
+
9
+ 为了支持 AgentScope 开源社区的长期健康发展,我们将公开、透明地维护 AgentScope 的开发计划。
10
+
11
+ **路线图公开**。AgentScope 的开发计划会发布在 [GitHub Projects 页面](https://github.com/orgs/agentscope-ai/projects/2),并持续更新。路线图会反映 AgentScope 的技术发展方向,由核心开发团队对 AgentScope 的整体设计与质量负责。
12
+
13
+ **社区可认领的任务**。Projects 页面 / Issues 中标有 `help wanted` 的条目对所有人开放。如果你有兴趣参与某一项:
14
+
15
+ - 请在对应 issue 下评论,告知准备认领
16
+ - 这样可以避免重复劳动,也方便我们尽早协作
17
+
18
+ **成为核心开发者**。我们欢迎想要更深入参与、共同塑造 AgentScope 的开发者。我们会在合适的时机邀请投入度高的贡献者成为核心开发者。
19
+ 成为核心开发者也意味着更频繁的参与到 AgentScope 的开发工作中,包括:
20
+
21
+ - 更加频繁的设计讨论、代码评审与多轮迭代,需要持续的时间和精力投入
22
+ - 为保证 AgentScope 的整体一致性与可靠性,核心团队保留对项目技术方向与质量标准的把控
23
+
24
+ **提出新想法**。针对有路线图上还没有的想法,请新建 issue 描述提议。核心开发团队会尽可能地及时回复并一起讨论可行的推进路径。
25
+
26
+ ## 2. 在贡献中负责任地使用 AI
27
+
28
+ AgentScope 欢迎使用 AI 编码助手的贡献者——Claude Code、Cursor、Codex、Copilot 等等。我们只要求**负责任地使用**。AgentScope 依靠评审者的时间和社区信任运转,AI 辅助的贡献需要兼顾两者。
29
+
30
+ 涉及 AI 时的几条要求:
31
+
32
+ - **作者是人,不是 AI**。在 push 之前,请逐行阅读 diff,运行代码,确认理解了**改了什么**和**为什么改**。“Claude Code / Cursor / Codex 就是这么写的”并不是一个合适的理由,也不利于开源社区的健康发展。
33
+
34
+ - **创建 PR 前先自行评审 AI 生成的代码**。所有人的事件都是宝贵的资源,请不要将没有审阅过的 AI 代码/改动直接丢给维护者评审。
35
+
36
+ - **保持 PR 原子化**。不要提交 AI 一次性生成的 10K+ 行 PR,这种 PR 无法评审,会被拒绝。请把改动拆成若干个聚焦原子化功能的、具有单一目标的 PR。
37
+
38
+ - **AI 生成代码遵守同样的原则**。AgentScope 的所有开发原则——模块化、惰性导入、约定式提交、测试覆盖、不破坏 API——对 AI 辅助代码同等适用。
39
+
40
+ 简而言之:AI 让我们的开发更快,但确保合入 AgentScope 的代码质量责任仍在贡献者本人。
41
+
42
+ ## 3. 贡献流程
43
+
44
+ 端到端的贡献流程如下。
45
+
46
+ ### 第 1 步:认领或创建 issue
47
+
48
+ 在写代码之前,先找到或创建对应的 issue。
49
+
50
+ - **基于已有任务**:浏览 [Projects](https://github.com/orgs/agentscope-ai/projects/2) 与 [Issues](https://github.com/agentscope-ai/agentscope/issues) 中标有 `help wanted` 的条目(参见 [§1](#1-开发路线图与参与方式)),在 issue 下评论认领后再开始。
51
+ - **提出新想法**:新建 issue 描述问题、方案与设计上的取舍。等待核心开发团队反馈后再开始实现,避免事后大规模返工。
52
+
53
+ ### 第 2 步:Fork 仓库并创建开发分支
54
+
55
+ 1. 在 GitHub 上 fork [agentscope-ai/agentscope](https://github.com/agentscope-ai/agentscope)。
56
+ 2. clone 自己的 fork 并添加 upstream 远端:
57
+ ```bash
58
+ git clone https://github.com/<your-username>/agentscope.git
59
+ cd agentscope
60
+ git remote add upstream https://github.com/agentscope-ai/agentscope.git
61
+ ```
62
+ 3. 基于最新的 `main` 创建主题分支:
63
+ ```bash
64
+ git checkout main
65
+ git pull upstream main
66
+ git checkout -b feat/<short-description>
67
+ ```
68
+
69
+ ### 第 3 步:搭建本地环境
70
+
71
+ AgentScope 要求 **Python 3.11+**(详见 `pyproject.toml`)。
72
+
73
+ ```bash
74
+ # 创建隔离环境(这里用 uv,也可用 virtualenv / conda)
75
+ uv venv
76
+ source .venv/bin/activate
77
+
78
+ # 以可编辑模式安装 AgentScope,并带上 dev extras
79
+ pip install -e ".[dev]"
80
+ # 等价的 uv 写法:
81
+ uv pip install -e ".[dev]"
82
+
83
+ # 启用 git pre-commit hooks
84
+ pre-commit install
85
+ ```
86
+
87
+ `dev` extra 会拉入 `pre-commit`、`pytest`、文档工具链以及 `full` extra(包含 `models`、`service`、`storage`)。一次安装即可获得开发与运行完整测试套件所需的一切。
88
+
89
+ ### 第 4 步:开发
90
+
91
+ 写代码时遵守的几条约定:
92
+
93
+ - **可选依赖必须惰性导入**。任何**未列在 `pyproject.toml` 的 `[project.dependencies]` 中**的依赖——也就是来自可选 extra(`gemini`、`ollama`、`xai`、`service`、`storage` 等)的——**必须在使用点惰性导入**,而不是放在模块顶部:
94
+ ```python
95
+ def some_function():
96
+ import google.genai # 来自 `gemini` extra,惰性导入
97
+ # ... 在这里使用 google.genai
98
+ ```
99
+ 这样保持 `import agentscope` 轻量,`ImportError` 只在实际用到该 extra 的功能时才抛出。如果改动需要引入全新的依赖,先决定它属于基础 `[project.dependencies]`(始终需要、保持精简)还是某个可选 extra,并在 issue 中讨论后再合入。
100
+
101
+ - **遵守项目代码风格**。pre-commit 会自动处理格式与大部分 lint 规则,请在提交前运行 pre-commit 来修复问题。
102
+
103
+ - **功能要配套写单元测试**。测试位于 `tests/` 下,沿用现有结构。依赖可选 extra 的测试(如 Redis、Ollama)在该 extra 未安装时应能干净 skip。
104
+
105
+ ### 第 5 步:跑 pre-commit、测试,并更新文档
106
+
107
+ 创建 PR 之前,请在本地运行如下的命令检查代码格式与功能:
108
+
109
+ ```bash
110
+ # 自动格式化与 lint
111
+ pre-commit run --all-files
112
+
113
+ # 单元测试
114
+ pytest tests
115
+ ```
116
+
117
+ 如果 pre-commit hook 失败,请修复格式问题(多数会自动修复),然后重新 commit。
118
+
119
+ **改代码的同时请更新文档**。
120
+
121
+ - AgentScope 文档放在独立仓库:**[agentscope-ai/docs](https://github.com/agentscope-ai/docs)**。如果改动影响用户可见行为——新模块、新公开 API、行为变化、教程——请在该仓库同步开一个配套 PR。
122
+ - 为新公开 API 更新 docstring 与示例片段。
123
+ - 如果改动影响新手上手或 AgentScope 的对外宣传内容,更新 `README.md`。
124
+
125
+ ### 第 6 步:提交与发起 PR
126
+
127
+ **Commit 信息格式**。我们遵循 [Conventional Commits](https://www.conventionalcommits.org/) 规范,便于阅读历史与自动生成 changelog。
128
+
129
+ ```
130
+ <type>(<scope>): <subject>
131
+ ```
132
+
133
+ **Type 列表:**
134
+ - `feat:` 新功能
135
+ - `fix:` bug 修复
136
+ - `docs:` 仅文档变更
137
+ - `style:` 不影响代码语义的改动(空白、格式等)
138
+ - `refactor:` 既不是修 bug 也不是加功能的代码改动
139
+ - `perf:` 性能优化
140
+ - `ci:` 增补或修正测试
141
+ - `chore:` 构建流程或辅助工具/库的变更
142
+
143
+ **示例:**
144
+ ```bash
145
+ feat(models): add support for Claude-3 model
146
+ fix(agent): resolve memory leak in ReActAgent
147
+ docs(readme): update installation instructions
148
+ refactor(formatter): simplify message formatting logic
149
+ ci(models): add unit tests for OpenAI integration
150
+ ```
151
+
152
+ **PR 标题格式**。PR 标题同样遵循 Conventional Commits 格式,并由 GitHub Actions 在针对 `main` 的 PR 上自动校验。标题不合规的 PR 会被阻止合入,直到修正为止。
153
+
154
+ ```
155
+ <type>(<scope>): <description>
156
+ ```
157
+
158
+ **要求:**
159
+ - 标题须以下列 type 之一开头:`feat`、`fix`、`docs`、`ci`、`refactor`、`test`、`chore`、`perf`、`style`、`build`、`revert`
160
+ - scope 可选,建议带上
161
+ - **scope 必须小写**——只允许小写字母、数字、连字符(`-`)和下划线(`_`)
162
+ - description 以小写字母开头
163
+ - 标题保持简洁、有信息量
164
+
165
+ **示例:**
166
+ ```
167
+ ✅ 合规:
168
+ feat(memory): add redis cache support
169
+ fix(agent): resolve memory leak in ReActAgent
170
+ docs(tutorial): update installation guide
171
+ ci(workflow): add PR title validation
172
+ refactor(my-feature): simplify logic
173
+
174
+ ❌ 不合规:
175
+ feat(Memory): add cache # scope 必须小写
176
+ feat(MEMORY): add cache # scope 必须小写
177
+ feat(MyFeature): add feature # scope 必须小写
178
+ ```
179
+
180
+ **发起 PR**。把分支 push 到自己的 fork,对 `agentscope-ai/agentscope:main` 发起 pull request。在 PR 描述里:
181
+
182
+ - 关联认领的 issue(`Fixes #123` 或 `Refs #123`)
183
+ - 概述改了什么、为什么改
184
+ - 标注任何破坏性改动、废弃项或迁移步骤
185
+ - 如果同时开了文档 PR,链接到 [agentscope-ai/docs](https://github.com/agentscope-ai/docs) 的对应 PR
186
+
187
+ ## 4. 重要事项
188
+
189
+ 开始贡献前需要了解的几条横向约束。模块特定的事项见下文对应模块指南。
190
+
191
+ - **非平凡改动先开 issue**。突然提交涉及大量文件、改动公开 API 或引入新模块的 PR 难以评审,多半会被拒。先在 issue 中讨论设计。
192
+ - **PR 保持聚焦、原子**。一个 PR 一个目的。不要把重构和功能、或功能和不相干的 bug 修复混在一起。
193
+ - **不擅自破坏公开 API**。能保持向后兼容就保持。无法避免的破坏性改动,在 PR 描述中清楚说明,并在同一个 PR 里更新受影响的示例和文档。
194
+ - **不绕过惰性导入原则**。可选依赖必须在使用点导入,不能放在模块顶部。
195
+ - **不随意引入依赖**。每个新依赖都是长期维护负担。如果只有一个模块用到,优先在该模块内部惰性导入。
196
+ - **不忽视 CI 失败**。pre-commit、类型检查、测试必须通过后再��起 review,不要把修复负担推给评审者。
197
+ - **保持尊重**。遵守行为准则。AgentScope 的评审风格直接但友善,对贡献者也是同样期待。
198
+
199
+ ## 5. 模块特定贡献指南
200
+
201
+ 下文覆盖社区贡献者最常扩展的模块。其他模块请先开 issue 协调。
202
+
203
+ ### Chat Model
204
+
205
+ AgentScope 中的一个 chat model 不只是一个类——要在 `Agent` 中可用,需要一组上下游配套实现。一个完整的 chat model 贡献需包含**以下全部**:
206
+
207
+ 1. **Credential 类**——位于 `agentscope.credential`,继承 `CredentialBase`。承载 API key、endpoint 及 SDK 所需的其他鉴权字段。
208
+ _参考:`agentscope/credential/_anthropic.py`_
209
+
210
+ 2. **Chat model 类**——位于 `agentscope.model.<provider>/`,继承 `ChatModelBase`。实现需覆盖:
211
+ - 流式与非流式两种模式
212
+ - Tools API 集成(function/tool calling)
213
+ - `tool_choice` 参数
214
+ - 适用时的 reasoning 模型支持
215
+
216
+ _参考:`agentscope/model/_anthropic/`_
217
+
218
+ 3. **Model card YAML**——位于 `agentscope.model.<provider>._models/`,每个支持的模型一份 YAML。必填字段:`name`、`label`、`status`、`input_types`、`output_types`、`context_size`、`output_size`。可选字段:`parameter_overrides`、`deprecated_at`。
219
+
220
+ 示例(`claude-sonnet-4-6.yaml`):
221
+ ```yaml
222
+ name: claude-sonnet-4-6
223
+ label: Claude Sonnet 4.6
224
+ status: active
225
+ input_types:
226
+ - text/plain
227
+ - image/jpeg
228
+ output_types:
229
+ - text/plain
230
+ context_size: 1000000
231
+ output_size: 65536
232
+ parameter_overrides:
233
+ max_tokens: {"maximum": 65536}
234
+ ```
235
+
236
+ 4. **Formatter 类**——位于 `agentscope.formatter`,均继承 `FormatterBase`。需要两种变体,因为部分 API 对多 agent 对话与单用户对话的处理方式不同:
237
+ - `<Provider>ChatFormatter` 处理单用户对话场景
238
+ - `<Provider>MultiAgentFormatter` 处理多 agent 场景
239
+
240
+ 每个 formatter 把 `Msg` 对象转换成对应 provider API 期望的请求格式。
241
+ _参考:`agentscope/formatter/_anthropic_formatter.py`_
242
+
243
+ > ⚠️ 只加 model 类、缺少配套 credential、model card YAML 与两种 formatter 变体的 PR 不会被合入。
244
+
245
+ ### Agent
246
+
247
+ AgentScope 目前只维护**一个核心 agent 类**——`agentscope.agent.Agent`——它整合了 AgentScope 库的全部功能(memory、tools、MCP、formatter、model 等)。
248
+
249
+ 特定领域或专用 agent 请作为 [example](#examples) 贡献,而不是在 `agentscope.agent` 中新增类。
250
+
251
+ 如果确信某个用例需要新的顶层 agent 类:
252
+
253
+ 1. **先开 issue**,描述用例并说明为什么组合现有 `Agent` 能力不够。
254
+ 2. **等核心团队的设计讨论**,再开始具体的代码实现。
255
+ 3. 未经事先讨论就引入新 agent 类的 PR 会被拒绝。
256
+
257
+ ### Workspace
258
+
259
+ Workspace 提供 agent 运行所需的运行时上下文(skills、scheduled tasks 等)。新增 workspace 后端需要两个类加配套文档:
260
+
261
+ 1. **Workspace 类**——位于 `agentscope.workspace`,继承 `WorkspaceBase`。实现该后端的存储与生命周期语义。
262
+ _参考:`agentscope/workspace/_local_workspace.py`(`LocalWorkspace`)_
263
+
264
+ 2. **Workspace manager 类**——位于 `agentscope/app/_manager/_workspace_manager.py`,继承 `WorkspaceManagerBase`。把 workspace 接入应用生命周期。
265
+ _参考:同文件中的 `LocalWorkspaceManager`_
266
+
267
+ 3. **文档**——在 [agentscope-ai/docs](https://github.com/agentscope-ai/docs) 配套发起 PR,说明该 workspace 的配置与使用方式。
268
+
269
+ ### Examples
270
+
271
+ 我们非常欢迎新增展示 AgentScope 能力的 example。
272
+
273
+ 主仓库 `examples/` 目录聚焦于**演示具体特性与能力**——简洁、教学性的参考实现。更完整、贴近生产形态的应用,请贡献到 **[agentscope-samples](https://github.com/agentscope-ai/agentscope-samples)**。
274
+
275
+ 新 example 放在自己的子目录下:
276
+
277
+ ```
278
+ examples/
279
+ └── <example-name>/
280
+ ├── main.py
281
+ ├── README.md # 说明 example 的目的、运行方式与预期输出
282
+ └── ...
283
+ ```
284
+
285
+ `examples/agent_service/` 是不错的参考起点。
286
+
287
+ ## 获取帮助
288
+
289
+ 需要协助或有问题,可以:
290
+
291
+ - 发起 [Discussion](https://github.com/agentscope-ai/agentscope/discussions)
292
+ - 在 [Issues](https://github.com/agentscope-ai/agentscope/issues) 中报告 bug
293
+ - 通过钉钉或 Discord 联系维护者(链接见 README.md)
294
+
295
+
296
+ ---
297
+
298
+ 感谢您为 AgentScope 所做的贡献!每一份努力都在为社区构建更好的开源工具。
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ RUN apt-get update && apt-get install -y \
4
+ curl git redis-server \
5
+ && rm -rf /var/lib/apt/lists/*
6
+
7
+ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
8
+ && apt-get install -y nodejs \
9
+ && npm install -g pnpm
10
+
11
+ WORKDIR /app
12
+ COPY . .
13
+
14
+ RUN pip install --no-cache-dir -e .
15
+ RUN pip install redis uvicorn fastapi
16
+
17
+ WORKDIR /app/examples/web_ui
18
+ RUN pnpm install
19
+ RUN pnpm build
20
+
21
+ WORKDIR /app
22
+ RUN chmod +x /app/start.sh
23
+
24
+ EXPOSE 7860
25
+ CMD ["/app/start.sh"]
LICENSE ADDED
@@ -0,0 +1,391 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2024 Alibaba
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
203
+
204
+
205
+
206
+ --------------------------------------------------------------------------------
207
+
208
+
209
+ Some codes of tests/run.py is modified from
210
+ https://github.com/alibaba/FederatedScope/blob/master/tests/run.py, which is
211
+ also licensed under the terms of the Apache 2.0.
212
+
213
+
214
+ --------------------------------------------------------------------------------
215
+
216
+ Code in src/agentscope/web/static/js/socket.io.js is adapted from
217
+ https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.3/socket.io.js (MIT License)
218
+
219
+ Copyright (c) 2014-2021 Guillermo Rauch
220
+
221
+ Permission is hereby granted, free of charge, to any person obtaining a copy
222
+ of this software and associated documentation files (the "Software"), to deal
223
+ in the Software without restriction, including without limitation the rights
224
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
225
+ copies of the Software, and to permit persons to whom the Software is
226
+ furnished to do so, subject to the following conditions:
227
+
228
+ The above copyright notice and this permission notice shall be included in all
229
+ copies or substantial portions of the Software.
230
+
231
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
232
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
233
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
234
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
235
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
236
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
237
+ SOFTWARE.
238
+
239
+ --------------------------------------------------------------------------------
240
+
241
+ Code in src/agentscope/web/static/js/jquery-3.3.1.min.js is adapted from
242
+ https://code.jquery.com/jquery-3.3.1.min.js (MIT License)
243
+
244
+ Copyright (c) JS Foundation and other contributors
245
+
246
+ Permission is hereby granted, free of charge, to any person obtaining a copy
247
+ of this software and associated documentation files (the "Software"), to deal
248
+ in the Software without restriction, including without limitation the rights
249
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
250
+ copies of the Software, and to permit persons to whom the Software is
251
+ furnished to do so, subject to the following conditions:
252
+
253
+ The above copyright notice and this permission notice shall be included in all
254
+ copies or substantial portions of the Software.
255
+
256
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
257
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
258
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
259
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
260
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
261
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
262
+ SOFTWARE.
263
+
264
+ --------------------------------------------------------------------------------
265
+
266
+ Code in src/agentscope/web/static/js/bootstrap.bundle.min.js is adapted from
267
+ https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.bundle.min.js
268
+ (MIT License)
269
+
270
+ Copyright (c) 2011-2019 The Bootstrap Authors (https://github
271
+ .com/twbs/bootstrap/graphs/contributors)
272
+
273
+ Permission is hereby granted, free of charge, to any person obtaining a copy
274
+ of this software and associated documentation files (the "Software"), to deal
275
+ in the Software without restriction, including without limitation the rights
276
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
277
+ copies of the Software, and to permit persons to whom the Software is
278
+ furnished to do so, subject to the following conditions:
279
+
280
+ The above copyright notice and this permission notice shall be included in all
281
+ copies or substantial portions of the Software.
282
+
283
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
284
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
285
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
286
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
287
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
288
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
289
+ SOFTWARE.
290
+
291
+ --------------------------------------------------------------------------------
292
+
293
+ Code in src/agentscope/web/static/js/bootstrap-table.min.js is adapted from
294
+ https://unpkg.com/bootstrap-table@1.18.0/dist/bootstrap-table.min.js (MIT
295
+ License)
296
+
297
+ Copyright (c) wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
298
+
299
+ Permission is hereby granted, free of charge, to any person obtaining a copy
300
+ of this software and associated documentation files (the "Software"), to deal
301
+ in the Software without restriction, including without limitation the rights
302
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
303
+ copies of the Software, and to permit persons to whom the Software is
304
+ furnished to do so, subject to the following conditions:
305
+
306
+ The above copyright notice and this permission notice shall be included in all
307
+ copies or substantial portions of the Software.
308
+
309
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
310
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
311
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
312
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
313
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
314
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
315
+ SOFTWARE.
316
+
317
+ --------------------------------------------------------------------------------
318
+
319
+ Code in src/agentscope/web/static/css/bootstrap.min.css is adapted from
320
+ https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css (MIT
321
+ License)
322
+
323
+ Copyright 2011-2019 The Bootstrap Authors
324
+ Copyright 2011-2019 Twitter, Inc.
325
+
326
+ Permission is hereby granted, free of charge, to any person obtaining a copy
327
+ of this software and associated documentation files (the "Software"), to deal
328
+ in the Software without restriction, including without limitation the rights
329
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
330
+ copies of the Software, and to permit persons to whom the Software is
331
+ furnished to do so, subject to the following conditions:
332
+
333
+ The above copyright notice and this permission notice shall be included in all
334
+ copies or substantial portions of the Software.
335
+
336
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
337
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
338
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
339
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
340
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
341
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
342
+ SOFTWARE.
343
+
344
+
345
+ --------------------------------------------------------------------------------
346
+
347
+ Fonts in src/agentscope/web/static/fonts/KRYPTON.ttf is adapted from
348
+ https://github.com/githubnext/monaspace (SIL Open Font License 1.1). These
349
+ fonts are distributed with their original license. See https://github
350
+ .com/githubnext/monaspace/blob/main/LICENSE for the full text of the license.
351
+ The following font families are included:
352
+
353
+ - Monaspace (with subfamilies: Krypton)
354
+
355
+ Copyright (c) 2023, GitHub https://github.com/githubnext/monaspace
356
+ with Reserved Font Name "Monaspace", including subfamilies: "Argon", "Neon",
357
+ "Xenon", "Radon", and "Krypton"
358
+
359
+ DISCLAIMER
360
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
361
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
362
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
363
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
364
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
365
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
366
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
367
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
368
+ OTHER DEALINGS IN THE FONT SOFTWARE.
369
+
370
+ --------------------------------------------------------------------------------
371
+
372
+ Fonts in src/agentscope/web/static/fonts/OSWALD.ttf is adapted from
373
+ https://fonts.google.com/specimen/Oswald (SIL Open Font License 1.1). These
374
+ fonts are distributed with their original license. See https://github
375
+ .com/googlefonts/OswaldFont/blob/main/OFL.txt for the full text of the license.
376
+
377
+ Copyright 2016 The Oswald Project Authors (https://github
378
+ .com/googlefonts/OswaldFont)
379
+
380
+ DISCLAIMER
381
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
382
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
383
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
384
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
385
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
386
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
387
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
388
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
389
+ OTHER DEALINGS IN THE FONT SOFTWARE.
390
+
391
+ --------------------------------------------------------------------------------
README.md CHANGED
@@ -1,10 +1,274 @@
1
- ---
2
- title: Agentscope
3
- emoji: 🏆
4
- colorFrom: yellow
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <img
3
+ src="https://img.alicdn.com/imgextra/i1/O1CN01nTg6w21NqT5qFKH1u_!!6000000001621-55-tps-550-550.svg"
4
+ alt="AgentScope Logo"
5
+ width="200"
6
+ />
7
+ </p>
8
+
9
+ <span align="center">
10
+
11
+ [**中文主页**](https://github.com/agentscope-ai/agentscope/blob/main/README_zh.md) | [**Documentation**](https://docs.agentscope.io/) | [**Roadmap**](https://github.com/orgs/agentscope-ai/projects/2/views/1)
12
+
13
+ </span>
14
+
15
+ <p align="center">
16
+ <a href="https://arxiv.org/abs/2402.14034">
17
+ <img
18
+ src="https://img.shields.io/badge/cs.MA-2402.14034-B31C1C?logo=arxiv&logoColor=B31C1C"
19
+ alt="arxiv"
20
+ />
21
+ </a>
22
+ <a href="https://pypi.org/project/agentscope/">
23
+ <img
24
+ src="https://img.shields.io/badge/python-3.11+-blue?logo=python"
25
+ alt="pypi"
26
+ />
27
+ </a>
28
+ <a href="https://pypi.org/project/agentscope/">
29
+ <img
30
+ src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpypi.org%2Fpypi%2Fagentscope%2Fjson&query=%24.info.version&prefix=v&logo=pypi&label=version"
31
+ alt="pypi"
32
+ />
33
+ </a>
34
+ <a href="https://discord.gg/eYMpfnkG8h">
35
+ <img
36
+ src="https://img.shields.io/badge/Discord-Join%20Us-5865F2?logo=discord&logoColor=white"
37
+ alt="discord"
38
+ />
39
+ </a>
40
+ <a href="https://docs.agentscope.io/">
41
+ <img
42
+ src="https://img.shields.io/badge/Docs-English%7C%E4%B8%AD%E6%96%87-blue?logo=markdown"
43
+ alt="docs"
44
+ />
45
+ </a>
46
+ <a href="./LICENSE">
47
+ <img
48
+ src="https://img.shields.io/badge/license-Apache--2.0-black"
49
+ alt="license"
50
+ />
51
+ </a>
52
+ </p>
53
+
54
+ <p align="center">
55
+ <img src="https://trendshift.io/api/badge/repositories/20310" alt="agentscope-ai%2Fagentscope | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
56
+ </p>
57
+
58
+ ## What is AgentScope 2.0?
59
+
60
+ AgentScope 2.0 is a production-ready, easy-to-use agent framework with essential abstractions that work with rising model capability and built-in support for .
61
+
62
+ - [**Event System** →](https://docs.agentscope.io/latest/en/building-blocks/message-and-event) A unified event bus to the frontend and human-in-the-loop support.
63
+ - [**Permission System** →](https://docs.agentscope.io/latest/en/building-blocks/permission-system) Fine-grained, configurable control over tools and resources.
64
+ - [**Multi-tenancy & Multi-session Service** →](https://docs.agentscope.io/latest/en/deploy/agent-service) Production-grade serving with isolation across tenants and sessions.
65
+ - [**Workspace / Sandbox Support** →](https://docs.agentscope.io/latest/en/building-blocks/workspace) Run tools and code in isolated environments, with built-in backends for local, Docker, and E2B.
66
+ - [**Extensible Middleware System** →](https://docs.agentscope.io/latest/en/building-blocks/middleware) Composable hooks to customize and extend the agent's reasoning-acting loop.
67
+
68
+ We design for increasingly agentic LLMs.
69
+ Our approach leverages the models' reasoning and tool use abilities
70
+ rather than constraining them with strict prompts and opinionated orchestrations.
71
+
72
+ <img src="assets/images/agentscope.png" alt="agentscope" width="100%"/>
73
+
74
+ ## News
75
+ <!-- BEGIN NEWS -->
76
+ - **[2026-06] `FEAT`:** Distributed & Multi-Tenancy & Multi-Session RAG service supported. [Docs](https://docs.agentscope.io/latest/en/deploy/agent-team)
77
+ - **[2026-06] `FEAT`:** RAG supported. [Example](https://github.com/agentscope-ai/agentscope/tree/main/examples/rag) | [Docs](https://docs.agentscope.io/latest/en/building-blocks/rag)
78
+ - **[2026-06] `INTE`:** Mem0 supported. [Example](https://github.com/agentscope-ai/agentscope/tree/main/examples/long_term_memory) | [Docs](https://docs.agentscope.io/latest/en)
79
+ - **[2026-06] `FEAT`:** Agent Team supported. [Example](https://github.com/agentscope-ai/agentscope/tree/main/examples/agent_service) | [Docs](https://docs.agentscope.io/latest/en/deploy/agent-team)
80
+ - **[2026-05] `RELS`:** AgentScope 2.0 released! [Docs](https://docs.agentscope.io/)
81
+ <!-- END NEWS -->
82
+
83
+ [More news →](./docs/NEWS.md)
84
+
85
+ ## Community
86
+
87
+ Welcome to join our community on
88
+
89
+ | [Discord](https://discord.gg/eYMpfnkG8h) | DingTalk |
90
+ |----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
91
+ | <img src="https://gw.alicdn.com/imgextra/i1/O1CN01hhD1mu1Dd3BWVUvxN_!!6000000000238-2-tps-400-400.png" width="100" height="100"> | <img src="./assets/images/dingtalk_qr_code.png" width="100" height="100"> |
92
+
93
+ ## Quickstart
94
+
95
+ ### Installation
96
+
97
+ > AgentScope requires **Python 3.11** or higher.
98
+
99
+ #### From PyPI
100
+
101
+ ```bash
102
+ uv pip install agentscope
103
+ # or
104
+ # pip install agentscope
105
+ ```
106
+
107
+ #### From source
108
+
109
+ ```bash
110
+ # Pull the source code from GitHub
111
+ git clone -b main https://github.com/agentscope-ai/agentscope.git
112
+
113
+ # Install the package in editable mode
114
+ cd agentscope
115
+
116
+ uv pip install -e .
117
+ # or
118
+ # pip install -e .
119
+ ```
120
+
121
+ ## Hello AgentScope!
122
+
123
+ Start your first agent with AgentScope 2.0:
124
+
125
+ ```python
126
+ from agentscope.agent import Agent
127
+ from agentscope.tool import Toolkit, Bash, Grep, Glob, Read, Write, Edit
128
+ from agentscope.credential import DashScopeCredential
129
+ from agentscope.model import DashScopeChatModel
130
+ from agentscope.message import UserMsg
131
+ from agentscope.event import EventType
132
+
133
+ import os, asyncio
134
+
135
+
136
+ async def main() -> None:
137
+ agent = Agent(
138
+ name="Friday",
139
+ system_prompt="You're a helpful assistant named Friday.",
140
+ model=DashScopeChatModel(
141
+ credential=DashScopeCredential(
142
+ api_key=os.environ["DASHSCOPE_API_KEY"]
143
+ ),
144
+ model="qwen3.6-plus",
145
+ ),
146
+ toolkit=Toolkit(
147
+ tools=[
148
+ Bash(),
149
+ Grep(),
150
+ Glob(),
151
+ Read(),
152
+ Write(),
153
+ Edit(),
154
+ ]
155
+ ),
156
+ )
157
+
158
+ async for evt in agent.reply_stream(UserMsg("Tony", "Hi, Friday!")):
159
+ # Handle the event stream, e.g., print the message, update UI, etc.
160
+ match evt.type:
161
+ case EventType.REPLY_START:
162
+ ...
163
+ case EventType.MODEL_CALL_START:
164
+ ...
165
+ case EventType.TEXT_BLOCK_START:
166
+ ...
167
+ case EventType.TEXT_BLOCK_DELTA:
168
+ ...
169
+ case EventType.TEXT_BLOCK_END:
170
+ ...
171
+
172
+ # Handle other event types
173
+
174
+ asyncio.run(main())
175
+ ```
176
+
177
+ ## Hello Agent Service!
178
+
179
+ An extensible FastAPI based **multi-tenancy**, **multi-session** agent service with pre-built Web UI in `examples/web_ui`
180
+
181
+ <table>
182
+ <tr>
183
+ <td align="center">
184
+ <img src="assets/images/team.gif" alt="Agent team" width="100%"/>
185
+ <br/>
186
+ <sub><b>Agent team</b> — a leader agent spawns workers and coordinates them through the built-in team tools.</sub>
187
+ </td>
188
+ </tr>
189
+ <tr>
190
+ <td align="center">
191
+ <img src="assets/images/task.gif" alt="Task planning" width="100%"/>
192
+ <br/>
193
+ <sub><b>Task planning</b> — the agent breaks complex work into a tracked plan and updates it as it goes.</sub>
194
+ </td>
195
+ </tr>
196
+ <tr>
197
+ <td align="center">
198
+ <img src="assets/images/permission_bypass.gif" alt="Permission control in bypass mode" width="100%"/>
199
+ <br/>
200
+ <sub><b>Permission control in bypass mode</b> — the agent runs end-to-end without pausing for tool-call confirmations.</sub>
201
+ </td>
202
+ </tr>
203
+ <tr>
204
+ <td align="center">
205
+ <img src="assets/images/bg_tool.gif" alt="Background task offloading" width="100%"/>
206
+ <br/>
207
+ <sub><b>Background task offloading</b> — a long-running tool moves to the background; its result later wakes the agent up and the conversation resumes.</sub>
208
+ </td>
209
+ </tr>
210
+ </table>
211
+
212
+ Run the following commands to start the agent service backend and the web UI:
213
+
214
+ ```bash
215
+ git clone -b main https://github.com/agentscope-ai/agentscope.git
216
+ cd agentscope/examples/agent_service
217
+
218
+ # start the agent service backend
219
+ python main.py
220
+ ```
221
+
222
+ Then open another terminal to start the web UI:
223
+
224
+ ```bash
225
+ cd agentscope/examples/web_ui
226
+
227
+ # start the webui
228
+ pnpm install
229
+ pnpm dev
230
+ ```
231
+
232
+
233
+ ## Contributing
234
+
235
+ We welcome contributions from the community! Please refer to our [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines
236
+ on how to contribute.
237
+
238
+ ## License
239
+
240
+ AgentScope is released under Apache License 2.0.
241
+
242
+ ## Publications
243
+
244
+ If you find our work helpful for your research or application, please cite our papers.
245
+
246
+ - [AgentScope 1.0: A Developer-Centric Framework for Building Agentic Applications](https://arxiv.org/abs/2508.16279)
247
+
248
+ - [AgentScope: A Flexible yet Robust Multi-Agent Platform](https://arxiv.org/abs/2402.14034)
249
+
250
+ ```
251
+ @article{agentscope_v1,
252
+ author = {Dawei Gao, Zitao Li, Yuexiang Xie, Weirui Kuang, Liuyi Yao, Bingchen Qian, Zhijian Ma, Yue Cui, Haohao Luo, Shen Li, Lu Yi, Yi Yu, Shiqi He, Zhiling Luo, Wenmeng Zhou, Zhicheng Zhang, Xuguang He, Ziqian Chen, Weikai Liao, Farruh Isakulovich Kushnazarov, Yaliang Li, Bolin Ding, Jingren Zhou}
253
+ title = {AgentScope 1.0: A Developer-Centric Framework for Building Agentic Applications},
254
+ journal = {CoRR},
255
+ volume = {abs/2508.16279},
256
+ year = {2025},
257
+ }
258
+
259
+ @article{agentscope,
260
+ author = {Dawei Gao, Zitao Li, Xuchen Pan, Weirui Kuang, Zhijian Ma, Bingchen Qian, Fei Wei, Wenhao Zhang, Yuexiang Xie, Daoyuan Chen, Liuyi Yao, Hongyi Peng, Zeyu Zhang, Lin Zhu, Chen Cheng, Hongzhu Shi, Yaliang Li, Bolin Ding, Jingren Zhou}
261
+ title = {AgentScope: A Flexible yet Robust Multi-Agent Platform},
262
+ journal = {CoRR},
263
+ volume = {abs/2402.14034},
264
+ year = {2024},
265
+ }
266
+ ```
267
+
268
+ ## Contributors
269
+
270
+ All thanks to our contributors:
271
+
272
+ <a href="https://github.com/agentscope-ai/agentscope/graphs/contributors">
273
+ <img src="https://contrib.rocks/image?repo=agentscope-ai/agentscope&max=999&columns=12&anon=1" />
274
+ </a>
README_zh.md ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <img
3
+ src="https://img.alicdn.com/imgextra/i1/O1CN01nTg6w21NqT5qFKH1u_!!6000000001621-55-tps-550-550.svg"
4
+ alt="AgentScope Logo"
5
+ width="200"
6
+ />
7
+ </p>
8
+
9
+ <span align="center">
10
+
11
+ [**English Homepage**](https://github.com/agentscope-ai/agentscope/blob/main/README.md) | [**文档**](https://docs.agentscope.io/) | [**路线图**](https://github.com/orgs/agentscope-ai/projects/2/views/1)
12
+
13
+ </span>
14
+
15
+ <p align="center">
16
+ <a href="https://arxiv.org/abs/2402.14034">
17
+ <img
18
+ src="https://img.shields.io/badge/cs.MA-2402.14034-B31C1C?logo=arxiv&logoColor=B31C1C"
19
+ alt="arxiv"
20
+ />
21
+ </a>
22
+ <a href="https://pypi.org/project/agentscope/">
23
+ <img
24
+ src="https://img.shields.io/badge/python-3.11+-blue?logo=python"
25
+ alt="pypi"
26
+ />
27
+ </a>
28
+ <a href="https://pypi.org/project/agentscope/">
29
+ <img
30
+ src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpypi.org%2Fpypi%2Fagentscope%2Fjson&query=%24.info.version&prefix=v&logo=pypi&label=version"
31
+ alt="pypi"
32
+ />
33
+ </a>
34
+ <a href="https://discord.gg/eYMpfnkG8h">
35
+ <img
36
+ src="https://img.shields.io/badge/Discord-Join%20Us-5865F2?logo=discord&logoColor=white"
37
+ alt="discord"
38
+ />
39
+ </a>
40
+ <a href="https://docs.agentscope.io/">
41
+ <img
42
+ src="https://img.shields.io/badge/Docs-English%7C%E4%B8%AD%E6%96%87-blue?logo=markdown"
43
+ alt="docs"
44
+ />
45
+ </a>
46
+ <a href="./LICENSE">
47
+ <img
48
+ src="https://img.shields.io/badge/license-Apache--2.0-black"
49
+ alt="license"
50
+ />
51
+ </a>
52
+ </p>
53
+
54
+ <p align="center">
55
+ <img src="https://trendshift.io/api/badge/repositories/20310" alt="agentscope-ai%2Fagentscope | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
56
+ </p>
57
+
58
+ ## 什么是 AgentScope 2.0?
59
+
60
+ AgentScope 2.0 是一款面向生产、易于使用的智能体框架,提供与不断进化的模型能力相匹配的核心抽象。
61
+
62
+ - [**事件系统** →](https://docs.agentscope.io/latest/zh/building-blocks/message-and-event) 统一的事件总线,服务于前端智能体应用与 human-in-the-loop 协作。
63
+ - [**权限系统** →](https://docs.agentscope.io/latest/zh/building-blocks/permission-system) 对工具和资源进行细粒度、可配置的控制。
64
+ - [**多租户与多会话服务** →](https://docs.agentscope.io/latest/zh/deploy/agent-service) 提供生产级服务,在租户与会话之间实现隔离。
65
+ - [**工作区 / 沙箱支持** →](https://docs.agentscope.io/latest/zh/building-blocks/workspace) 在隔离环境中运行工具和代码,内置支持本地文件系统、Docker 和 E2B 后端。
66
+ - [**可扩展中间件系统** →](https://docs.agentscope.io/latest/zh/building-blocks/middleware) 可组合的钩子系统,用于自定义和扩展智能体的推理-行动循环。
67
+
68
+ 我们为日益自主的大语言模型而设计。
69
+ 我们的方法是充分发挥模型的推理与工具调用能力,
70
+ 而不是用严格的提示词和固化的编排方式来束缚它们。
71
+
72
+ ## 为什么选择 AgentScope?
73
+
74
+ - **简单**:通过内置的 ReAct 智能体、工具、技能、人机协作干预、记忆、计划、实时语音、评估和模型微调,5 分钟即可开始构建你的智能体
75
+ - **可扩展**:丰富的生态系统集成,覆盖工具、记忆和可观测性;内置 MCP 和 A2A 支持;通过消息中心(MsgHub)实现灵活的多智能体编排和工作流
76
+ - **生产就绪**:支持本地部署、云端 Serverless 部署或 K8s 集群部署,并内置 OTel 支持
77
+
78
+ <img src="assets/images/agentscope.png" alt="agentscope" width="100%"/>
79
+
80
+ ## 新闻
81
+ <!-- BEGIN NEWS -->
82
+ - **[2026-06] `功能`:** 支持分布式 & 多租户 & 多会话 RAG 服务。 [文档](https://docs.agentscope.io/latest/en/deploy/agent-team)
83
+ - **[2026-06] `功能`:** 支持多模态 RAG。 [样例](https://github.com/agentscope-ai/agentscope/tree/main/examples/rag) | [文档](https://docs.agentscope.io/latest/en/building-blocks/rag)
84
+ - **[2026-06] `集成`:** 集成 Mem0 长期记忆。 [Example](https://github.com/agentscope-ai/agentscope/tree/main/examples/long_term_memory) | [Docs](https://docs.agentscope.io/latest/zh)
85
+ - **[2026-06] `功能`:** 支持 Agent Team。[样例](https://github.com/agentscope-ai/agentscope/tree/main/examples/agent_service) | [文档](https://docs.agentscope.io/latest/zh/deploy/agent-team)
86
+ - **[2026-05] `发布`:** AgentScope 2.0 已发布![文档](https://docs.agentscope.io/)
87
+ <!-- END NEWS -->
88
+
89
+ [更多新闻 →](./docs/NEWS_zh.md)
90
+
91
+ ## 社区
92
+
93
+ 欢迎加入我们的社区
94
+
95
+ | [Discord](https://discord.gg/eYMpfnkG8h) | 钉钉 |
96
+ |----------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
97
+ | <img src="https://gw.alicdn.com/imgextra/i1/O1CN01hhD1mu1Dd3BWVUvxN_!!6000000000238-2-tps-400-400.png" width="100" height="100"> | <img src="./assets/images/dingtalk_qr_code.png" width="100" height="100"> |
98
+
99
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
100
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
101
+ ## 📑 Table of Contents
102
+
103
+ - [快速开始](#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)
104
+ - [安装](#%E5%AE%89%E8%A3%85)
105
+ - [从 PyPI 安装](#%E4%BB%8E-pypi-%E5%AE%89%E8%A3%85)
106
+ - [从源码安装](#%E4%BB%8E%E6%BA%90%E7%A0%81%E5%AE%89%E8%A3%85)
107
+ - [Hello AgentScope!](#hello-agentscope)
108
+ - [智能体服务](#%E6%99%BA%E8%83%BD%E4%BD%93%E6%9C%8D%E5%8A%A1)
109
+ - [贡献](#%E8%B4%A1%E7%8C%AE)
110
+ - [许可](#%E8%AE%B8%E5%8F%AF)
111
+ - [论文](#%E8%AE%BA%E6%96%87)
112
+ - [贡献者](#%E8%B4%A1%E7%8C%AE%E8%80%85)
113
+
114
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
115
+
116
+ ## 快速开始
117
+
118
+ ### 安装
119
+
120
+ > AgentScope 需要 **Python 3.11** 或更高版本。
121
+
122
+ #### 从 PyPI 安装
123
+
124
+ ```bash
125
+ uv pip install agentscope
126
+ # 或者
127
+ # pip install agentscope
128
+ ```
129
+
130
+ #### 从源码安装
131
+
132
+ ```bash
133
+ # 从 GitHub 拉取源码
134
+ git clone -b main https://github.com/agentscope-ai/agentscope.git
135
+
136
+ # 以可编辑模式安装
137
+ cd agentscope
138
+
139
+ uv pip install -e .
140
+ # 或者
141
+ # pip install -e .
142
+ ```
143
+
144
+ ## Hello AgentScope!
145
+
146
+ 使用 AgentScope 2.0,启动你的第一个智能体:
147
+
148
+ ```python
149
+ from agentscope.agent import Agent
150
+ from agentscope.tool import Toolkit, Bash, Grep, Glob, Read, Write, Edit
151
+ from agentscope.credential import DashScopeCredential
152
+ from agentscope.model import DashScopeChatModel
153
+ from agentscope.message import UserMsg
154
+ from agentscope.event import EventType
155
+
156
+ import os, asyncio
157
+
158
+
159
+ async def main() -> None:
160
+ agent = Agent(
161
+ name="Friday",
162
+ system_prompt="You're a helpful assistant named Friday.",
163
+ model=DashScopeChatModel(
164
+ credential=DashScopeCredential(
165
+ api_key=os.environ["DASHSCOPE_API_KEY"]
166
+ ),
167
+ model="qwen3.6-plus",
168
+ ),
169
+ toolkit=Toolkit(
170
+ tools=[
171
+ Bash(),
172
+ Grep(),
173
+ Glob(),
174
+ Read(),
175
+ Write(),
176
+ Edit(),
177
+ ]
178
+ ),
179
+ )
180
+
181
+ async for evt in agent.reply_stream(UserMsg("Tony", "Hi, Friday!")):
182
+ # 处理事件流,例如打印消息、更新 UI 等
183
+ match evt.type:
184
+ case EventType.REPLY_START:
185
+ ...
186
+ case EventType.MODEL_CALL_START:
187
+ ...
188
+ case EventType.TEXT_BLOCK_START:
189
+ ...
190
+ case EventType.TEXT_BLOCK_DELTA:
191
+ ...
192
+ case EventType.TEXT_BLOCK_END:
193
+ ...
194
+
195
+ # 处理其他事件类型
196
+
197
+ asyncio.run(main())
198
+ ```
199
+
200
+ ## 智能体服务
201
+
202
+ 一个基于 FastAPI 的可扩展**多租户**、**多会话**智能体服务,并在 `examples/web_ui` 中提供预构建的 Web UI
203
+
204
+ <table>
205
+ <tr>
206
+ <td align="center">
207
+ <img src="assets/images/team.gif" alt="智能体团队" width="100%"/>
208
+ <br/>
209
+ <sub><b>智能体团队</b> —— leader 智能体派生 worker,并通过内置的团队工具进行协调。</sub>
210
+ </td>
211
+ </tr>
212
+ <tr>
213
+ <td align="center">
214
+ <img src="assets/images/task.gif" alt="任务规划" width="100%"/>
215
+ <br/>
216
+ <sub><b>任务规划</b> —— 智能体将复杂工作拆解为可追踪的计划,并在执行过程中持续更新。</sub>
217
+ </td>
218
+ </tr>
219
+ <tr>
220
+ <td align="center">
221
+ <img src="assets/images/permission_bypass.gif" alt="bypass 模式下的权限控制" width="100%"/>
222
+ <br/>
223
+ <sub><b>bypass 模式下的权限控制</b> —— 智能体端到端运行,无需为工具调用确认而暂停。</sub>
224
+ </td>
225
+ </tr>
226
+ <tr>
227
+ <td align="center">
228
+ <img src="assets/images/bg_tool.gif" alt="后台任务卸载" width="100%"/>
229
+ <br/>
230
+ <sub><b>工具后台执行</b> —— 长时间运行的工具被转入后台;其结果稍后唤醒智能体并恢复对话。</sub>
231
+ </td>
232
+ </tr>
233
+ </table>
234
+
235
+ 运行以下命令启动智能体服务后端和 Web UI:
236
+
237
+ ```bash
238
+ git clone -b main https://github.com/agentscope-ai/agentscope.git
239
+ cd agentscope/examples/agent_service
240
+
241
+ # 启动智能体服务后端
242
+ python main.py
243
+ ```
244
+
245
+ 然后打开另一个终端启动 Web UI:
246
+
247
+ ```bash
248
+ cd agentscope/examples/web_ui
249
+
250
+ # 启动 webui
251
+ pnpm install
252
+ pnpm dev
253
+ ```
254
+
255
+
256
+
257
+ ## 贡献
258
+
259
+ 我们欢迎社区的贡献!请参阅我们的 [贡献指南](./CONTRIBUTING_zh.md) 了解如何贡献。
260
+
261
+ ## 许可
262
+
263
+ AgentScope 基于 Apache License 2.0 发布。
264
+
265
+ ## 论文
266
+
267
+ 如果我们的工作对您的研究或应用有帮助,请引用我们的论文。
268
+
269
+ - [AgentScope 1.0: A Developer-Centric Framework for Building Agentic Applications](https://arxiv.org/abs/2508.16279)
270
+
271
+ - [AgentScope: A Flexible yet Robust Multi-Agent Platform](https://arxiv.org/abs/2402.14034)
272
+
273
+ ```
274
+ @article{agentscope_v1,
275
+ author = {Dawei Gao, Zitao Li, Yuexiang Xie, Weirui Kuang, Liuyi Yao, Bingchen Qian, Zhijian Ma, Yue Cui, Haohao Luo, Shen Li, Lu Yi, Yi Yu, Shiqi He, Zhiling Luo, Wenmeng Zhou, Zhicheng Zhang, Xuguang He, Ziqian Chen, Weikai Liao, Farruh Isakulovich Kushnazarov, Yaliang Li, Bolin Ding, Jingren Zhou}
276
+ title = {AgentScope 1.0: A Developer-Centric Framework for Building Agentic Applications},
277
+ journal = {CoRR},
278
+ volume = {abs/2508.16279},
279
+ year = {2025},
280
+ }
281
+
282
+ @article{agentscope,
283
+ author = {Dawei Gao, Zitao Li, Xuchen Pan, Weirui Kuang, Zhijian Ma, Bingchen Qian, Fei Wei, Wenhao Zhang, Yuexiang Xie, Daoyuan Chen, Liuyi Yao, Hongyi Peng, Zeyu Zhang, Lin Zhu, Chen Cheng, Hongzhu Shi, Yaliang Li, Bolin Ding, Jingren Zhou}
284
+ title = {AgentScope: A Flexible yet Robust Multi-Agent Platform},
285
+ journal = {CoRR},
286
+ volume = {abs/2402.14034},
287
+ year = {2024},
288
+ }
289
+ ```
290
+
291
+ ## 贡献者
292
+
293
+ 感谢所有贡献者:
294
+
295
+ <a href="https://github.com/agentscope-ai/agentscope/graphs/contributors">
296
+ <img src="https://contrib.rocks/image?repo=agentscope-ai/agentscope&max=999&columns=12&anon=1" />
297
+ </a>
assets/images/agentscope.png ADDED

Git LFS Details

  • SHA256: 8fa7ccfe9d89c193ac020ce82bd76b9abdf291d4a99dcf2ec2f3576035ea9e1b
  • Pointer size: 131 Bytes
  • Size of remote file: 652 kB
assets/images/bg_tool.gif ADDED

Git LFS Details

  • SHA256: cd3b85e47aca9e3439387dfe0b0cde734579ac5f6e16c1d1127ea81883f714f7
  • Pointer size: 131 Bytes
  • Size of remote file: 287 kB
assets/images/dingtalk_qr_code.png ADDED
assets/images/permission_bypass.gif ADDED

Git LFS Details

  • SHA256: 246dfa7e21fc4694d8e1a1b70fe423d1445bf1d7a3b81b79636a65c9d89e2019
  • Pointer size: 131 Bytes
  • Size of remote file: 683 kB
assets/images/task.gif ADDED

Git LFS Details

  • SHA256: 7166b727a591bdb3a58c680316f0c33b0ff56abe6456aa29c6dc25869ec00be2
  • Pointer size: 132 Bytes
  • Size of remote file: 1.69 MB
assets/images/team.gif ADDED

Git LFS Details

  • SHA256: e94b414cddd551150138ff0db48a12f0789c2d9ad148e6a174467d0d8cd23441
  • Pointer size: 132 Bytes
  • Size of remote file: 1.34 MB
docs/NEWS.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- This is the source of truth for all NEWS items. -->
2
+ <!-- The first 10 items are automatically synced to README.md and README_zh.md via GitHub Actions. -->
3
+ <!-- To update news in READMEs, modify this file and push to trigger the workflow. -->
4
+
5
+ - **[2026-06] `FEAT`:** Agentic Memory supported. [Example](https://github.com/agentscope-ai/agentscope/tree/main/examples/long_term_memory/agentic_memory) | [Docs](https://docs.agentscope.io/latest/en/building-blocks/long-term-memory)
6
+ - **[2026-06] `FEAT`:** Distributed & Multi-Tenancy & Multi-Session RAG service supported. [Docs](https://docs.agentscope.io/latest/en/deploy/agent-team)
7
+ - **[2026-06] `FEAT`:** RAG supported. [Example](https://github.com/agentscope-ai/agentscope/tree/main/examples/rag) | [Docs](https://docs.agentscope.io/latest/en/building-blocks/rag)
8
+ - **[2026-06] `INTE`:** Mem0 supported. [Example](https://github.com/agentscope-ai/agentscope/tree/main/examples/long_term_memory) | [Docs](https://docs.agentscope.io/latest/en)
9
+ - **[2026-06] `FEAT`:** Agent Team supported. [Example](https://github.com/agentscope-ai/agentscope/tree/main/examples/agent_service) | [Docs](https://docs.agentscope.io/latest/en/deploy/agent-team)
10
+ - **[2026-05] `RELS`:** AgentScope 2.0 released! [Docs](https://docs.agentscope.io/)
docs/NEWS_zh.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- This is the source of truth for all NEWS items. -->
2
+ <!-- The first 10 items are automatically synced to README.md and README_zh.md via GitHub Actions. -->
3
+ <!-- To update news in READMEs, modify this file and push to trigger the workflow. -->
4
+
5
+ - **[2026-06] `功能`:** 支持 Agentic Memory。 [样例](https://github.com/agentscope-ai/agentscope/tree/main/examples/long_term_memory/agentic_memory) | [文档](https://docs.agentscope.io/latest/zh/building-blocks/long-term-memory)
6
+ - **[2026-06] `功能`:** 支持分布式 & 多租户 & 多会话 RAG 服务。 [文档](https://docs.agentscope.io/latest/en/deploy/agent-team)
7
+ - **[2026-06] `功能`:** 支持多模态 RAG。 [样例](https://github.com/agentscope-ai/agentscope/tree/main/examples/rag) | [文档](https://docs.agentscope.io/latest/en/building-blocks/rag)
8
+ - **[2026-06] `集成`:** 集成 Mem0 长期记忆。 [Example](https://github.com/agentscope-ai/agentscope/tree/main/examples/long_term_memory) | [Docs](https://docs.agentscope.io/latest/zh)
9
+ - **[2026-06] `功能`:** 支持 Agent Team。[样例](https://github.com/agentscope-ai/agentscope/tree/main/examples/agent_service) | [文档](https://docs.agentscope.io/latest/zh/deploy/agent-team)
10
+ - **[2026-05] `发布`:** AgentScope 2.0 已发布![文档](https://docs.agentscope.io/)
docs/changelog.md ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CHANGELOG of v1.0.0
2
+
3
+ > ➡️ change; ✅ new feature; ❌ deprecate
4
+
5
+ The overall changes from v0.x.x to v1.0.0 are summarized below.
6
+
7
+ ## Overview
8
+ - ✅ Support asynchronous execution throughout the library
9
+ - ✅ Support tools API thoroughly
10
+
11
+
12
+ ## ✨Session
13
+ - ✅ Support automatic state management
14
+ - ✅ Support session/application-level state management
15
+
16
+
17
+ ## ✨Tracing
18
+ - ✅ Support OpenTelemetry-based tracing
19
+ - ✅ Support third-party tracing platforms, e.g. Arize-Phoenix, Langfuse, etc.
20
+
21
+
22
+ ## ✨MCP
23
+ - ✅ Support both client- and function-level control over MCP by a new MCP module
24
+ - ✅ Support both "pay-as-you-go" and persistent session management
25
+ - ✅ Support streamable HTTP, SSE and StdIO transport protocols
26
+
27
+
28
+ ## ✨Memory
29
+ - ✅ Support long-term memory by providing a `LongTermMemoryBase` class
30
+ - ✅ Provide a Mem0-based long-term memory implementation
31
+ - ✅ Support both static- and agent-controlled long-term memory modes
32
+
33
+
34
+ ## Formatter
35
+ - ✅ Support prompt construction/formatting with token count estimation
36
+ - ✅ Support tools API in multi-agent prompt formatting
37
+
38
+
39
+ ## Model
40
+ - ❌ Deprecate model configuration, use explicit object instantiation instead
41
+ - ✅ Provide a new `ModelResponse` class for structured model responses
42
+ - ✅ Support asynchronous model invocation
43
+ - ✅ Support reasoning models
44
+ - ✅ Support any combination of streaming/non-streaming, reasoning/non-reasoning and tools API
45
+
46
+
47
+ ## Agent
48
+ - ❌ Deprecate `DialogAgent`, `DictDialogAgent` and prompt-based ReAct agent class
49
+ - ➡️ Expose memory, formatter interfaces to the agent's constructor in ReActAgent
50
+ - ➡️ Unify the signature of pre- and post- agent hooks
51
+ - ✅ Support pre-/post-reasoning and pre-/post-acting hooks in ReActAgent class
52
+ - ✅ Support asynchronous agent execution
53
+ - ✅ Support interrupting agent's replying and customized interruption handling
54
+ - ✅ Support automatic state management
55
+ - ✅ Support parallel tool calls
56
+ - ✅ Support two-modes long-term memory in ReActAgent class
57
+
58
+
59
+ ## Tool
60
+ - ✅ Provide a more powerful `Toolkit` class for tools management
61
+ - ✅ Provide a new `ToolResponse` class for structured and multimodal tool responses
62
+ - ✅ Support group-wise tool management
63
+ - ✅ Support agent to manage tools by itself
64
+ - ✅ Support post-processing of tool responses
65
+ - Tool function
66
+ - ✅ Support both async and sync functions
67
+ - ✅ Support both streaming and non-streaming return
68
+
69
+
70
+ ## Evaluation
71
+ - ✅ Support ReAct agent-oriented evaluation
72
+ - ✅ Support Ray-based distributed and concurrent evaluation
73
+ - ✅ Support statistical analysis over evaluation results
74
+
75
+
76
+ ## AgentScope Studio
77
+ - ✅ Support runtime tracing
78
+ - ✅ Provide a built-in copilot agent named Friday
79
+
80
+
81
+ ## Logging
82
+ - ❌ Deprecate `loguru` and use Python native `logging` module instead
83
+
84
+
85
+ ## Distribution
86
+ - ❌ Deprecate distribution functionality momentarily, a new distribution module is coming soon
87
+
88
+
89
+ ## RAG
90
+ - ❌ Deprecate RAG functionality momentarily, a new RAG module is coming soon
91
+
92
+
93
+ ## Parsers
94
+ - ❌ Deprecate parsers module
95
+
96
+
97
+ ## WebBrowser
98
+ - ❌ Deprecate the `WebBrowser` class and shift to MCP-based web browsing
docs/roadmap.md ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Roadmap
2
+
3
+ ## Long-term Goals
4
+
5
+ Offering **agent-oriented programming (AOP)** as a new programming paradigm to organize the design and implementation of next-generation LLM-empowered applications.
6
+
7
+ ## Current Focus (January 2026 - )
8
+
9
+ ### 🎙️ Voice Agent
10
+
11
+ **Voice agents** are a domain we are highly focused on, and AgentScope will continue to invest in this direction.
12
+
13
+ AgentScope aims to build **production-ready** voice agents rather than demonstration prototypes. This means our voice agents will:
14
+
15
+ - Support **production-grade** deployment, including seamless frontend integration
16
+ - Support **tool invocation**, not just voice conversations
17
+ - Support **multi-agent** voice interactions
18
+
19
+ #### Development Roadmap
20
+
21
+ Our development strategy for voice agents consists of **three progressive milestones**:
22
+
23
+ 1. **TTS Models** → 2. **Multimodal Models** → 3. **Real-time Multimodal Models**
24
+
25
+ ---
26
+
27
+ #### Phase 1: TTS (Text-to-Speech) Models
28
+
29
+ - **Build TTS model base class infrastructure**
30
+ - Design and implement a unified TTS model base class
31
+ - Establish standardized interfaces for TTS model integration
32
+
33
+ - **Horizontal API expansion**
34
+ - Support mainstream TTS APIs (e.g., OpenAI TTS, Google TTS, Azure TTS, ElevenLabs, etc.)
35
+ - Ensure consistent behavior across different TTS providers
36
+
37
+ ---
38
+
39
+ #### Phase 2: Multimodal Models (Non-Realtime)
40
+
41
+ - **Enable ReAct agents with multimodal support**
42
+ - Integrate multimodal models (e.g., qwen3-omni, gpt-audio) into existing ReAct agent framework
43
+ - Support audio input/output in non-realtime mode
44
+
45
+ - **Advanced multimodal agent capabilities**
46
+ - Enable tool invocation within multimodal conversations
47
+ - Support multi-agent workflows with multimodal communication
48
+
49
+ ---
50
+
51
+ #### Phase 3: Real-time Multimodal Models
52
+
53
+
54
+ - **Beyond request-response**: Explore streaming, interrupt handling, and concurrent multimodal processing
55
+ - **New programming paradigms**: Design agent programming models specifically tailored for real-time interactions
56
+ - **Production readiness**: Ensure low-latency performance, stability, and scalability for production deployment
57
+
58
+ ### 🛠️ Agent Skill
59
+
60
+ Provide **production-ready** agent skill integration solutions.
61
+
62
+ ### 🌐 Ecosystem Expansion
63
+
64
+ - **A2UI (Agent-to-UI)**: Enable seamless agent-to-user interface interactions
65
+ - **A2A (Agent-to-Agent)**: Enhance agent-to-agent communication capabilities
66
+
67
+ ### 🚀 Agentic RL
68
+
69
+ - Support using [Tinker](https://tinker-docs.thinkingmachines.ai/) backend to tune agent applications on devices without GPU.
70
+ - Support tuning agent applications based on their run history.
71
+ - Integrate with AgentScope Runtime to provide better environment abstraction.
72
+ - Add more tutorials and examples on how to build complex judge functions with the help of evaluation module.
73
+ - Add more tutorials and examples on data selection and augmentation.
74
+
75
+ ### 📈 Code Quality
76
+
77
+ Continuous refinement and improvement of code quality and maintainability.
78
+
79
+ # Completed Milestones
80
+
81
+ ### AgentScope V1.0.0 Roadmap
82
+
83
+ We are deeply grateful for the continuous support from the open-source community that has witnessed AgentScope's
84
+ growth. Throughout our journey, we have maintained **developer-centric transparency** as our core principle,
85
+ which will continue to guide our future development.
86
+
87
+ As the AI agent ecosystem rapidly evolves, we recognize the need to adapt AgentScope to meet emerging trends and
88
+ requirements. We are excited to announce the upcoming release of AgentScope v1.0.0, which marks a significant shift
89
+ towards deployment-focused and secondary development direction. This new version will provide comprehensive support for agent developers
90
+ with enhanced deployment capabilities and practical features. Specifically, the update will include:
91
+
92
+ - ✨New Features
93
+ - 🛠️ Tool/MCP
94
+ - Support both sync/async tool functions
95
+ - Support streaming tool function
96
+ - Support parallel execution of tool functions
97
+ - Provide more flexible support for the MCP server
98
+
99
+ - 💾 Memory
100
+ - Enhance the existing short-term memory
101
+ - Support long-term memory
102
+
103
+ - 🤖 Agent
104
+ - Provide powerful ReAct-based out-of-the-box agents
105
+
106
+ - 👨‍💻 Development
107
+ - Provide enhanced AgentScope Studio with visual components for developing, tracing and debugging
108
+ - Provide a built-in copilot for developing/drafting AgentScope applications
109
+
110
+ - 🔍 Evaluation
111
+ - Provide built-in benchmarking and evaluation toolkit for agents
112
+ - Support result visualization
113
+
114
+ - 🏗️ Deployment
115
+ - Support asynchronous agent execution
116
+ - Support session/state management
117
+ - Provide sandbox for tool execution
118
+
119
+ Stay tuned for our detailed release notes and beta version, which will be available soon. Follow our GitHub
120
+ repository and official channels for the latest updates. We look forward to your valuable feedback and continued
121
+ support in shaping the future of AgentScope.
examples/agent_service/README.md ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Agent Service
2
+
3
+ Agent service is a FastAPI-based, multi-tenant and multi-session service built with AgentScope 2.0.
4
+
5
+ This example demonstrates
6
+
7
+ - how to set up the agent service with Redis storage, and
8
+ - how to launch the service and its companion Web UI
9
+
10
+ Details about the agent service please refer to the [tutorial](https://docs.agentscope.io/latest/en/deploy/agent-service).
11
+
12
+ ## Prerequisites
13
+
14
+ - Python ≥ 3.11
15
+ - Node.js ≥ 20 with `npx`
16
+ - [optional] Gaode/AMap API key in `AMAP_API_KEY` (for the `amap` MCP)
17
+
18
+ ## Quickstart
19
+
20
+ Install AgentScope from PyPI or source:
21
+
22
+ ```bash
23
+ uv pip install agentscope[full]
24
+ # or
25
+ # uv pip install -e [full]
26
+ ```
27
+
28
+ Install Redis and start it as backend storage:
29
+
30
+ ```bash
31
+ # macOS (Homebrew)
32
+ brew install redis
33
+ brew services start redis
34
+
35
+ # Linux (systemd)
36
+ sudo apt install redis-server
37
+ sudo systemctl start redis-server
38
+
39
+ # Docker (cross-platform)
40
+ docker run --rm -p 6379:6379 redis:7
41
+ ```
42
+
43
+ Start the agent service:
44
+
45
+ ```bash
46
+ cd examples/agent_service
47
+
48
+ python main.py
49
+ ```
50
+
51
+ Launch the Web UI in a separate terminal to experience a chat-style interface:
52
+
53
+ ```bash
54
+ cd examples/web_ui/
55
+
56
+ pnpm install
57
+ # or npm install
58
+
59
+ # Run in dev mode
60
+ pnpm dev
61
+ ```
62
+
63
+ After that, you can set the API endpoint `http://localhost:8000` in the Web UI and start experiencing the agent service.
64
+
65
+ <img src="https://gw.alicdn.com/imgextra/i2/O1CN01Phmg1G1brIVC8WXyU_!!6000000003518-2-tps-2938-1736.png" alt="Web UI Screenshot" width="100%">
66
+
67
+ ## What Next
68
+
69
+ - You can customize the service in `main.py` by adding your own MCPs, middlewares, or workspace manager implementations.
70
+
71
+ - Experience the agent service, including
72
+ - human-in-the-loop interactions & permission system
73
+ <img src="https://gw.alicdn.com/imgextra/i1/O1CN01vGGiBw20agWwpzmjy_!!6000000006866-2-tps-2934-1732.png" alt="Permission System" width="100%">
74
+
75
+ - schedule tasks
76
+ <img src="https://gw.alicdn.com/imgextra/i1/O1CN01Xi3Qw71E2haKKu4z0_!!6000000000294-2-tps-2932-1738.png" alt="Schedule Tasks" width="100%">
77
+
78
+ - and more! (stay tuned for future updates)
examples/agent_service/main.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """The example script to start the agent service."""
3
+ import os
4
+
5
+ import uvicorn
6
+ from fastapi.middleware import Middleware
7
+ from fastapi.middleware.cors import CORSMiddleware
8
+
9
+ from agentscope.app import create_app, SubAgentTemplate
10
+ from agentscope.app.message_bus import InMemoryMessageBus
11
+ from agentscope.app.rag.knowledge_base_manager import CollectionPerKbManager
12
+ from agentscope.app.storage import RedisStorage
13
+ from agentscope.app.workspace_manager import LocalWorkspaceManager
14
+ from agentscope.mcp import MCPClient, StdioMCPConfig, HttpMCPConfig
15
+ from agentscope.permission import PermissionContext, PermissionMode
16
+ from agentscope.rag import QdrantStore
17
+
18
+ default_mcps = [
19
+ MCPClient(
20
+ name="browser-use",
21
+ mcp_config=StdioMCPConfig(
22
+ command="npx",
23
+ args=["@playwright/mcp@latest"],
24
+ ),
25
+ is_stateful=True,
26
+ ),
27
+ ]
28
+
29
+ if os.getenv("AMAP_API_KEY"):
30
+ default_mcps.append(
31
+ MCPClient(
32
+ name="amap",
33
+ mcp_config=HttpMCPConfig(
34
+ url=f"https://mcp.amap.com/mcp?key="
35
+ f"{os.environ['AMAP_API_KEY']}",
36
+ ),
37
+ is_stateful=False,
38
+ ),
39
+ )
40
+
41
+ storage = RedisStorage(
42
+ host="localhost",
43
+ port=6379,
44
+ )
45
+
46
+ vector_store = QdrantStore(location=":memory:")
47
+
48
+ app = create_app(
49
+ storage=storage,
50
+ message_bus=InMemoryMessageBus(),
51
+ # -- To use a Redis-backed message bus instead (recommended for
52
+ # -- multi-process / production deployments), uncomment the lines
53
+ # -- below and replace the InMemoryMessageBus() above:
54
+ #
55
+ # from agentscope.app.message_bus import RedisMessageBus
56
+ # message_bus=RedisMessageBus(
57
+ # host="localhost",
58
+ # port=6379,
59
+ # ),
60
+ workspace_manager=LocalWorkspaceManager(
61
+ basedir=os.path.join(
62
+ os.path.dirname(os.path.abspath(__file__)),
63
+ "workspaces",
64
+ ),
65
+ # The default MCP servers that will be added into the workspace
66
+ default_mcps=default_mcps,
67
+ ),
68
+ # Knowledge base feature — backed by an in-memory Qdrant store. The
69
+ # CollectionPerKbManager allocates one collection per knowledge base,
70
+ # so any embedding dimension is allowed.
71
+ knowledge_base_manager=CollectionPerKbManager(
72
+ storage=storage,
73
+ vector_store=vector_store,
74
+ ),
75
+ # Customize your own subagent templates
76
+ custom_subagent_templates=[
77
+ SubAgentTemplate(
78
+ type="explorer",
79
+ description=(
80
+ "Read-only agents specialized in exploration tasks. It can "
81
+ "read files but cannot modify, create, or delete them. Use "
82
+ "this agent type when you need to investigate the codebase, "
83
+ "understand its structure, or gather information from files "
84
+ "to support planning—without making any changes."
85
+ ),
86
+ system_prompt_template="""You are {member_name}, an explorer \
87
+ agent in team '{team_name}' led by {leader_name}.
88
+
89
+ Team purpose: {team_description}
90
+
91
+ Your role: {member_description}
92
+
93
+ ## Responsibilities
94
+ - Complete the exploration tasks assigned by the team leader.
95
+ - You are read-only: you may inspect files and the codebase, but you must \
96
+ never modify, create, or delete anything.
97
+
98
+ ## Reporting
99
+ - Always report the task result back to {leader_name} using the TeamSay \
100
+ tool, whether the task succeeds or fails.
101
+ - Keep your private reasoning private; only share conclusions and findings \
102
+ that the leader needs.
103
+
104
+ Note: `TeamSay` is your ONLY channel to communicate with {leader_name} and \
105
+ the other team members. Any other output you produce is invisible to them, \
106
+ so anything you want them to see MUST be sent through `TeamSay`.""",
107
+ permission_context=PermissionContext(
108
+ # Read-only
109
+ mode=PermissionMode.EXPLORE,
110
+ ),
111
+ ),
112
+ ],
113
+ extra_middlewares=[
114
+ Middleware(
115
+ CORSMiddleware,
116
+ allow_origins=["*"],
117
+ allow_methods=["*"],
118
+ allow_headers=["*"],
119
+ ),
120
+ ],
121
+ )
122
+
123
+
124
+ if __name__ == "__main__":
125
+ # Start the service
126
+ uvicorn.run(
127
+ "main:app",
128
+ host="0.0.0.0",
129
+ port=8000,
130
+ reload=True,
131
+ )
examples/long_term_memory/agentic_memory/README.md ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Agentic Memory Middleware
2
+
3
+ This example demonstrates `AgenticMemoryMiddleware`, a long-term memory middleware backed by human-readable Markdown files.
4
+
5
+ No vector database or embedding model is required.
6
+
7
+ ## What the demo shows
8
+
9
+ `main.py` runs a single Agent against one workspace directory for two turns:
10
+
11
+ 1. **Turn 1 — persist**
12
+ - Receives mock user input containing durable user information.
13
+ - Is explicitly asked to remember that information.
14
+ - Uses the built-in `Read` / `Write` tools to create or update files under `demo_workspace/Memory`.
15
+
16
+ 2. **Turn 2 — recall**
17
+ - The same Agent instance is asked about the earlier user information.
18
+ - Answers from the Markdown memory files persisted on disk by the middleware.
19
+
20
+ After the first turn, the script prints the generated Markdown files so you can inspect exactly what was persisted.
21
+
22
+ ## Quickstart
23
+
24
+ Install the dependencies by the following commands:
25
+
26
+ ```bash
27
+ git clone -b main https://github.com/agentscope-ai/agentscope
28
+
29
+ uv pip install agentscope
30
+ # or from source
31
+ # uv pip install -e .
32
+ ```
33
+
34
+ Run the example with the commands:
35
+
36
+ ```bash
37
+ cd agentscope/examples/long_term_memory/agentic_memory
38
+ export DASHSCOPE_API_KEY=sk-...; python main.py
39
+ ```
40
+
41
+ The demo workspace is created at:
42
+
43
+ ```text
44
+ examples/long_term_memory/agentic_memory/demo_workspace/
45
+ ```
46
+
47
+ ## Markdown layout
48
+
49
+ The middleware creates this directory automatically:
50
+
51
+ ```text
52
+ <workdir>/Memory/
53
+ `-- MEMORY.md
54
+ ```
55
+
56
+ The Agent should write each durable memory into its own Markdown file with frontmatter, then add a short pointer to `MEMORY.md`:
57
+
58
+ ```markdown
59
+ ---
60
+ name: User profile
61
+ description: User lives in Hangzhou and prefers concise Chinese answers
62
+ type: user
63
+ ---
64
+
65
+ Alice Chen lives in Hangzhou and prefers concise Chinese answers.
66
+ ```
67
+
68
+ `MEMORY.md` is an index, not the memory body:
69
+
70
+ ```markdown
71
+ - [User profile](user_profile.md) — User location and answer-style preference.
72
+ ```
73
+
74
+ On future turns, `MEMORY.md` is always included in the system prompt. The middleware can then select relevant topic files by filename and frontmatter description and inject their contents as a hint.
75
+
76
+ ## Notes
77
+
78
+ - Memory is workspace-scoped: reuse the same `workdir` to reuse the same Markdown memory.
79
+ - A fresh Agent instance can still recall previous facts because they are stored on disk, not in `Agent.state`.
80
+ - The Agent is responsible for deciding what to save when the user asks it to remember something.
81
+ - `MEMORY.md` should stay concise because it is included in every system prompt.
82
+ - Topic files are ordinary Markdown and can be inspected, edited, committed, copied, or deleted with normal filesystem tools.
83
+
examples/long_term_memory/agentic_memory/main.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """AgenticMemoryMiddleware end-to-end demo.
3
+
4
+ The demo uses a single Agent with the filesystem-backed long-term memory
5
+ middleware and the built-in ``Read`` / ``Write`` tools across two turns:
6
+
7
+ 1. The Agent receives mock user input that explicitly asks it to remember
8
+ durable user information. The middleware injects memory instructions and the
9
+ Agent writes Markdown files under ``demo_workspace/Memory``.
10
+ 2. The same Agent is then asked to recall the earlier user information. The
11
+ answer is grounded by the Markdown files persisted on disk by the
12
+ middleware.
13
+
14
+ Requires:
15
+ pip install agentscope
16
+ export DASHSCOPE_API_KEY=sk-...
17
+ """
18
+ import asyncio
19
+ import os
20
+ import shutil
21
+ from pathlib import Path
22
+
23
+ from pydantic import SecretStr
24
+
25
+ from agentscope.agent import Agent
26
+ from agentscope.credential import DashScopeCredential
27
+ from agentscope.event import (
28
+ TextBlockDeltaEvent,
29
+ ToolCallDeltaEvent,
30
+ ToolCallStartEvent,
31
+ ToolResultEndEvent,
32
+ ToolResultTextDeltaEvent,
33
+ )
34
+ from agentscope.message import UserMsg
35
+ from agentscope.middleware import AgenticMemoryMiddleware
36
+ from agentscope.model import DashScopeChatModel
37
+ from agentscope.permission import AdditionalWorkingDirectory, PermissionMode
38
+ from agentscope.tool import Read, Toolkit, Write
39
+
40
+
41
+ RESET_DEMO_WORKSPACE = True
42
+ DEMO_ROOT = Path(__file__).with_name("demo_workspace")
43
+
44
+ FIRST_USER_MESSAGE = """
45
+ Please remember these durable facts for future conversations in this
46
+ workspace:
47
+
48
+ - My name is Alice Chen.
49
+ - I live in Hangzhou.
50
+ - I prefer concise Chinese answers.
51
+ - When evaluating examples, I like seeing a fresh Agent instance prove that
52
+ long-term memory was persisted outside the current conversation state.
53
+
54
+ Use the filesystem memory instructions in your system prompt: create or update
55
+ a topic Markdown memory file with frontmatter, and update MEMORY.md with a
56
+ short pointer to that file. Read MEMORY.md first if you need to update it.
57
+ """.strip()
58
+
59
+ SECOND_USER_MESSAGE = """
60
+ What do you remember about my name, location, answer style, and how I like
61
+ examples to demonstrate long-term memory? Read the relevant memory files if
62
+ you need details before answering.
63
+ """.strip()
64
+
65
+
66
+ def _configure_demo_permissions(agent: Agent, workspace_root: Path) -> None:
67
+ """Allow the demo Agent to read and write inside the demo workspace.
68
+
69
+ Args:
70
+ agent (`Agent`):
71
+ The Agent whose permission context should be configured.
72
+ workspace_root (`Path`):
73
+ The directory containing the demo memory files.
74
+ """
75
+ agent.state.permission_context.mode = PermissionMode.ACCEPT_EDITS
76
+ agent.state.permission_context.working_directories[
77
+ str(workspace_root)
78
+ ] = AdditionalWorkingDirectory(
79
+ path=str(workspace_root),
80
+ source="file-system-memory-demo",
81
+ )
82
+
83
+
84
+ def _build_agent(model: DashScopeChatModel, workspace_root: Path) -> Agent:
85
+ """Build a fresh Agent attached to one filesystem memory workspace.
86
+
87
+ Args:
88
+ model (`DashScopeChatModel`):
89
+ The chat model used by both the Agent and memory relevance
90
+ selection.
91
+ workspace_root (`Path`):
92
+ The directory that stores ``Memory/MEMORY.md`` and topic files.
93
+
94
+ Returns:
95
+ `Agent`:
96
+ A newly initialized Agent instance.
97
+ """
98
+ memory = AgenticMemoryMiddleware(workdir=str(workspace_root))
99
+ agent = Agent(
100
+ name="memory_assistant",
101
+ system_prompt=(
102
+ "You are a concise assistant. When the user asks you to remember "
103
+ "durable preferences or profile facts, persist them using the "
104
+ "filesystem memory instructions. Use the Read and Write tools for "
105
+ "memory files."
106
+ ),
107
+ model=model,
108
+ toolkit=Toolkit(tools=[Read(), Write()]),
109
+ middlewares=[memory],
110
+ )
111
+ _configure_demo_permissions(agent, workspace_root)
112
+ return agent
113
+
114
+
115
+ async def _run_turn(agent: Agent, text: str) -> str:
116
+ """Run one streamed turn and print tool activity.
117
+
118
+ Args:
119
+ agent (`Agent`):
120
+ The Agent to run.
121
+ text (`str`):
122
+ The user message.
123
+
124
+ Returns:
125
+ `str`:
126
+ The concatenated assistant text response.
127
+ """
128
+ tool_names: dict[str, str] = {}
129
+ tool_args: dict[str, str] = {}
130
+ tool_results: dict[str, str] = {}
131
+ reply_parts: list[str] = []
132
+
133
+ async for event in agent.reply_stream(UserMsg("alice", text)):
134
+ if isinstance(event, ToolCallStartEvent):
135
+ tool_names[event.tool_call_id] = event.tool_call_name
136
+ tool_args[event.tool_call_id] = ""
137
+ tool_results[event.tool_call_id] = ""
138
+ elif isinstance(event, ToolCallDeltaEvent):
139
+ tool_args[event.tool_call_id] += event.delta
140
+ elif isinstance(event, ToolResultTextDeltaEvent):
141
+ tool_results[event.tool_call_id] += event.delta
142
+ elif isinstance(event, ToolResultEndEvent):
143
+ tool_id = event.tool_call_id
144
+ name = tool_names.pop(tool_id, "<unknown>")
145
+ arguments = tool_args.pop(tool_id, "")
146
+ result = tool_results.pop(tool_id, "")
147
+ print(f"[tool] {name}({arguments}) -> {event.state}")
148
+ for line in result.splitlines():
149
+ print(f" {line}")
150
+ elif isinstance(event, TextBlockDeltaEvent):
151
+ reply_parts.append(event.delta)
152
+
153
+ return "".join(reply_parts)
154
+
155
+
156
+ def _print_memory_files(workspace_root: Path) -> None:
157
+ """Print the Markdown files persisted by the memory middleware.
158
+
159
+ Args:
160
+ workspace_root (`Path`):
161
+ The demo workspace root.
162
+ """
163
+ memory_root = workspace_root / "Memory"
164
+ print(f"\n[Markdown memory files] {memory_root}")
165
+ if not memory_root.exists():
166
+ print(" The Memory directory has not been created yet.")
167
+ return
168
+
169
+ for path in sorted(memory_root.rglob("*.md")):
170
+ relative = path.relative_to(workspace_root)
171
+ print(f"\n--- {relative} ---")
172
+ print(path.read_text(encoding="utf-8").strip())
173
+
174
+
175
+ def _print_soft_verification(workspace_root: Path) -> None:
176
+ """Print a lightweight check that expected memory keywords were saved.
177
+
178
+ Args:
179
+ workspace_root (`Path`):
180
+ The demo workspace root.
181
+ """
182
+ memory_root = workspace_root / "Memory"
183
+ combined = (
184
+ "\n".join(
185
+ path.read_text(encoding="utf-8", errors="replace")
186
+ for path in sorted(memory_root.rglob("*.md"))
187
+ )
188
+ if memory_root.exists()
189
+ else ""
190
+ )
191
+ checks = {
192
+ "MEMORY.md exists": (memory_root / "MEMORY.md").exists(),
193
+ "mentions Alice Chen": "Alice Chen" in combined,
194
+ "mentions Hangzhou": "Hangzhou" in combined,
195
+ "mentions concise Chinese answers": (
196
+ "concise Chinese" in combined or "Chinese answers" in combined
197
+ ),
198
+ }
199
+
200
+ print("\n[Soft verification]")
201
+ for label, ok in checks.items():
202
+ print(f" {'PASS' if ok else 'WARN'} - {label}")
203
+
204
+
205
+ async def main() -> None:
206
+ """Run the agentic memory demo."""
207
+ api_key = os.environ["DASHSCOPE_API_KEY"]
208
+
209
+ if RESET_DEMO_WORKSPACE:
210
+ print(f"=== resetting demo workspace: {DEMO_ROOT} ===")
211
+ shutil.rmtree(DEMO_ROOT, ignore_errors=True)
212
+ else:
213
+ print(f"=== reusing demo workspace: {DEMO_ROOT} ===")
214
+ DEMO_ROOT.mkdir(parents=True, exist_ok=True)
215
+
216
+ model = DashScopeChatModel(
217
+ credential=DashScopeCredential(api_key=SecretStr(api_key)),
218
+ model="qwen3.7-max",
219
+ stream=False,
220
+ )
221
+
222
+ print("\n=== Turn 1: ask the Agent to persist user memory ===")
223
+ agent = _build_agent(model, DEMO_ROOT)
224
+ print(f"[user]\n{FIRST_USER_MESSAGE}\n")
225
+ first_reply = await _run_turn(agent, FIRST_USER_MESSAGE)
226
+ print(f"\n[assistant]\n{first_reply}")
227
+
228
+ _print_memory_files(DEMO_ROOT)
229
+ _print_soft_verification(DEMO_ROOT)
230
+
231
+ print("\n=== Turn 2: ask the same Agent to recall memory ===")
232
+ print(f"[user]\n{SECOND_USER_MESSAGE}\n")
233
+ second_reply = await _run_turn(agent, SECOND_USER_MESSAGE)
234
+ print(f"\n[assistant]\n{second_reply}")
235
+
236
+
237
+ if __name__ == "__main__":
238
+ asyncio.run(main())
examples/long_term_memory/mem0/README.md ADDED
@@ -0,0 +1,387 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mem0 middleware example
2
+
3
+ One runnable demo (`oss_demo.py`) showing the
4
+ [mem0](https://github.com/mem0ai/mem0) middleware plugged into an
5
+ `agentscope.agent.Agent`. Drives two consecutive agent sessions for
6
+ the same `user_id` so mem0's cross-session memory effect is visible,
7
+ and prints each middleware contribution (retrieval / tool call /
8
+ write-back) inline so you can see when each path fires.
9
+
10
+ The demo defaults to the **OSS backend** (open-source mem0,
11
+ self-hosted via local Qdrant) with mem0 driven by AgentScope's own
12
+ DashScope chat + embedding model — no separate OpenAI key needed
13
+ by mem0. To run it against the hosted **mem0
14
+ Platform** instead, swap the `Mem0Middleware(...)` construction for
15
+ the alternative shown inline (look for the
16
+ ``# For the hosted mem0 Platform, swap …`` comment in `oss_demo.py`)
17
+ — the rest of the demo is identical.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ # mem0 is an optional AgentScope dependency — pull it via the extra:
23
+ pip install "agentscope[mem0]" # resolves to mem0ai>=2.0.0,<3.0.0
24
+ # (equivalent to `pip install agentscope mem0ai>=2.0.0,<3.0.0`)
25
+
26
+ export DASHSCOPE_API_KEY=sk-... # OSS path
27
+ # Platform path (only if you switch):
28
+ # export MEM0_API_KEY=m0-...
29
+ # export OPENAI_API_KEY=sk-... # only needed if your agent's chat model is OpenAI
30
+ ```
31
+
32
+ ## Import path
33
+
34
+ `Mem0Middleware` is exported from the middleware package:
35
+
36
+ ```python
37
+ from agentscope.middleware import Mem0Middleware
38
+ from agentscope.tool import Toolkit
39
+ ```
40
+
41
+ ## Three construction paths
42
+
43
+ ```python
44
+ # 1. Models — build a local OSS AsyncMemory wired to your AgentScope
45
+ # chat + embedding model. mem0 defaults for everything else.
46
+ Mem0Middleware(
47
+ user_id="alice",
48
+ chat_model=my_chat_model,
49
+ embedding_model=my_embedding_model,
50
+ mode="both",
51
+ )
52
+
53
+ # 2. Models + custom mem0_config — same as (1), but start from your
54
+ # customized MemoryConfig (custom vector store, history DB,
55
+ # reranker, ...). `chat_model` / `embedding_model` always WIN:
56
+ # if mem0_config already specifies an .llm or .embedder, it gets
57
+ # OVERWRITTEN by the AgentScope adapter built from your model.
58
+ # Every other field of mem0_config (vector_store, history_db_path,
59
+ # reranker, etc.) is preserved as-is.
60
+ Mem0Middleware(
61
+ user_id="alice",
62
+ chat_model=my_chat_model,
63
+ embedding_model=my_embedding_model,
64
+ mem0_config=MemoryConfig(
65
+ vector_store=VectorStoreConfig(
66
+ provider="qdrant",
67
+ config={"host": "my-qdrant", "port": 6333},
68
+ ),
69
+ history_db_path="/data/mem0_history.db",
70
+ ),
71
+ mode="both",
72
+ )
73
+
74
+ # 3. Client — bring your own pre-built mem0 client. Accepts EITHER
75
+ # backend: `mem0.AsyncMemory` (open-source / self-hosted) or
76
+ # `mem0.AsyncMemoryClient` (hosted Platform). Use this when you
77
+ # want full control over the mem0 setup — custom subclass, a
78
+ # pre-warmed client shared across many agents, exotic config
79
+ # that doesn't fit the `build_mem0_config` helper, etc.
80
+ #
81
+ # OSS backend (you assemble the AsyncMemory yourself):
82
+ Mem0Middleware(
83
+ user_id="alice",
84
+ client=AsyncMemory(), # or AsyncMemory.from_config({...})
85
+ mode="both",
86
+ )
87
+
88
+ # Hosted Platform backend:
89
+ Mem0Middleware(
90
+ user_id="alice",
91
+ client=AsyncMemoryClient(api_key="m0-..."),
92
+ mode="both",
93
+ )
94
+ ```
95
+
96
+ Precedence and validation matrix:
97
+
98
+ | `client` | `mem0_config` | `chat_model` | `embedding_model` | Behavior |
99
+ |:-:|:-:|:-:|:-:|---|
100
+ | ✓ | — | — | — | Use `client` as-is. |
101
+ | ✓ | any | any | any | Use `client`; the other three are ignored, and a `WARNING` log lists which kwargs got dropped. |
102
+ | — | ✓ | — | — | Wrap `mem0_config` in an `AsyncMemory`, no overrides. |
103
+ | — | ✓ | ✓ | — | Wrap + override `.llm` with the AgentScope adapter; keep `.embedder` from `mem0_config`. |
104
+ | — | ✓ | — | ✓ | Wrap + override `.embedder` only; keep `.llm` from `mem0_config`. |
105
+ | — | ✓ | ✓ | ✓ | Wrap + override both `.llm` and `.embedder` (other fields of `mem0_config` preserved). |
106
+ | — | — | ✓ | ✓ | Build a fresh `MemoryConfig` (mem0 defaults for vector store / history DB) with the AgentScope adapters wired in. |
107
+ | — | — | ✓ | — | ❌ `ValueError` — `chat_model` and `embedding_model` must be passed together when `mem0_config` is omitted. |
108
+ | — | — | — | ✓ | ❌ Same. |
109
+ | — | — | — | — | ❌ `ValueError` — need one of: `client`, `mem0_config`, or both `chat_model` + `embedding_model`. |
110
+
111
+ Why the "client wins" and "config override" paths exist:
112
+
113
+ - **`client` wins** lets one `Mem0Middleware(...)` call
114
+ shape work for both library callers (who pass AgentScope models)
115
+ and production setups (who supply a pre-built `client`). The
116
+ `WARNING` log makes any mismatch visible without crashing.
117
+ - **Config override of `mem0_config.llm` / `.embedder`** lets you
118
+ keep one canonical `MemoryConfig` template (custom vector store,
119
+ history DB, reranker, …) and swap just the LLM / embedder per
120
+ call site by passing `chat_model` / `embedding_model`.
121
+
122
+ ## How the middleware controls memory
123
+
124
+ The `mode` parameter selects one of three patterns. They differ by
125
+ **what the LLM sees** and **what fires automatically**:
126
+
127
+ ### `static_control`
128
+ The middleware does the work, the agent is unaware. Mirroring
129
+ AgentScope 1.x's `ReActAgent._retrieve_from_long_term_memory`:
130
+
131
+ 1. **`on_reply` (pre)** queries mem0 with the latest user message
132
+ and pre-fetches the results.
133
+ 2. **At `ReplyStartEvent`** — which fires right after the agent has
134
+ ingested the new user input into `state.context` and before the
135
+ reasoning loop starts — the middleware appends an
136
+ `AssistantMsg(name="memory", ...)` to `state.context`. This puts
137
+ the memory note IMMEDIATELY after the user's new message, matching
138
+ v1's placement (it ran right after `self.memory.add(msg)`).
139
+ 3. **`on_reply` (post)** writes the new `(user, assistant)` exchange
140
+ back to mem0.
141
+
142
+ The injected memory message **persists** in the agent's context
143
+ across turns. Long sessions accumulate one per turn that retrieved
144
+ anything; if that becomes a token concern, post-process with
145
+ `compress_context` or write your own middleware to pop them.
146
+
147
+ ### `agent_control`
148
+ The middleware lists two tools — `search_memory(keywords, limit)` and
149
+ `add_memory(thinking, content)` — and otherwise stays out of the way.
150
+ Pass them into the agent's toolkit explicitly when constructing the
151
+ agent:
152
+
153
+ ```python
154
+ mw = Mem0Middleware(..., mode="agent_control")
155
+ agent = Agent(
156
+ ...,
157
+ toolkit=Toolkit(tools=await mw.list_tools()),
158
+ middlewares=[mw],
159
+ )
160
+ ```
161
+
162
+ The system prompt gets a short nudge telling the agent that memory
163
+ tools exist; the actual per-tool usage guidance comes through the
164
+ standard tool schema. No automatic retrieval or write-back.
165
+
166
+ ### `both` (default)
167
+ Both patterns are active simultaneously: memories are auto-retrieved
168
+ and appended to the agent's context as an assistant note, AND the
169
+ tools (with their system-prompt hint) are exposed for explicit
170
+ on-demand search / save. This matches AgentScope 1.x's
171
+ `ReActAgent.long_term_memory_mode` default.
172
+
173
+ ## Sharing one middleware across agents
174
+
175
+ The local OSS mem0 backend uses on-disk Qdrant by default, and Qdrant
176
+ takes an **exclusive lock** on the storage folder
177
+ (``/tmp/qdrant`` by default). Two ``Mem0Middleware`` instances each
178
+ built from ``chat_model`` + ``embedding_model`` would each construct
179
+ their own ``AsyncMemory`` → second one crashes on the lock:
180
+
181
+ ```
182
+ RuntimeError: Storage folder /tmp/qdrant is already accessed by
183
+ another instance of Qdrant client.
184
+ ```
185
+
186
+ Fix: build **one** ``Mem0Middleware`` instance and pass it to every
187
+ agent that should share the same memory namespace:
188
+
189
+ ```python
190
+ mw = Mem0Middleware(
191
+ user_id="alice",
192
+ chat_model=chat_model,
193
+ embedding_model=embedding_model,
194
+ mode="both",
195
+ )
196
+ agent_a = Agent(
197
+ ...,
198
+ toolkit=Toolkit(tools=await mw.list_tools()),
199
+ middlewares=[mw],
200
+ )
201
+ agent_b = Agent(
202
+ ...,
203
+ toolkit=Toolkit(tools=await mw.list_tools()),
204
+ middlewares=[mw],
205
+ )
206
+ ```
207
+
208
+ This is what the demo does. The memory tools receive the live
209
+ `AgentState` at call time, and the middleware resolves the active
210
+ agent by `state.session_id`, so sharing one middleware across agents
211
+ is safe.
212
+
213
+ If you genuinely need a separate Qdrant store per agent, pass a
214
+ ``mem0_config`` with a distinct ``vector_store.config.path`` or
215
+ ``collection_name`` for each one.
216
+
217
+ ### Recommended: run Qdrant in Docker (especially on Windows)
218
+
219
+ The local on-disk Qdrant works for single-process demos but is
220
+ brittle in real deployments — and **outright painful on Windows**,
221
+ where the filesystem-lock semantics differ from Unix and the
222
+ exclusive-lock failure mode is harder to recover from. For anything
223
+ beyond a single-process Linux/macOS sandbox, run Qdrant as a service:
224
+
225
+ ```bash
226
+ docker run -p 6333:6333 -p 6334:6334 \
227
+ -v $(pwd)/qdrant_storage:/qdrant/storage \
228
+ qdrant/qdrant
229
+ ```
230
+
231
+ Then point mem0 at it instead of the on-disk path:
232
+
233
+ ```python
234
+ from mem0.configs.base import MemoryConfig
235
+ from mem0.vector_stores.configs import VectorStoreConfig
236
+
237
+ mem0_cfg = MemoryConfig(
238
+ vector_store=VectorStoreConfig(
239
+ provider="qdrant",
240
+ config={
241
+ "collection_name": "mem0",
242
+ "host": "localhost", # the Docker container
243
+ "port": 6333,
244
+ "embedding_model_dims": 1536,
245
+ },
246
+ ),
247
+ )
248
+ Mem0Middleware(
249
+ user_id="alice",
250
+ chat_model=chat_model,
251
+ embedding_model=embedding_model,
252
+ mem0_config=mem0_cfg,
253
+ )
254
+ ```
255
+
256
+ Benefits over on-disk:
257
+
258
+ - No file-lock contention — multiple Python processes can connect.
259
+ - Survives across runs without manual file cleanup.
260
+ - Same shape works for remote Qdrant (Qdrant Cloud, your own
261
+ Kubernetes deployment) — just change ``host`` / ``port`` /
262
+ ``api_key``.
263
+
264
+ ## Memory scoping (`user_id` × `agent_id`)
265
+
266
+ mem0 tags every stored memory with the `user_id` and `agent_id`
267
+ filter values passed at `add` time, and searches by AND-matching those
268
+ tags. The middleware exposes the agent dimension via the
269
+ `scope_search_by_agent` flag (default `True`):
270
+
271
+ | `scope_search_by_agent` | What `add` tags the memory with | What `search` filters by | Effect |
272
+ | --- | --- | --- | --- |
273
+ | `True` (default) | `user_id` + `agent_id` | `user_id` + `agent_id` | Strict per-agent silos. Agent A's memories invisible to agent B for the same user. |
274
+ | `False` | `user_id` + `agent_id` (unchanged) | `user_id` only | Read-broad, write-narrow. All agents for the same user share a memory pool, but each memory still records which agent wrote it (visible in mem0 metadata). |
275
+
276
+ `agent_id` defaults to `agent.name`. Override via `agent_id="..."` or
277
+ `agent_id=lambda agent: ...` on the middleware constructor.
278
+
279
+ When to relax `scope_search_by_agent`:
280
+
281
+ - One user has multiple specialized agents (research / coding /
282
+ scheduling) that should benefit from each other's discoveries about
283
+ the user.
284
+ - An agent's `name` might change across deployments but you want the
285
+ memory to persist across name changes.
286
+
287
+ ### A note on agent-centric extraction (currently unreachable)
288
+
289
+ mem0 v2's extraction prompt
290
+ ([`ADDITIVE_EXTRACTION_PROMPT`](https://github.com/mem0ai/mem0/blob/main/mem0/configs/prompts.py))
291
+ has a conditional suffix that switches framing from user-centric
292
+ ("User stated X") to **agent-centric** ("Agent was informed of X" /
293
+ "Agent recommended Y"). It's gated on
294
+ `is_agent_scoped = bool(filters.agent_id) and not filters.user_id` —
295
+ i.e. only when `agent_id` is provided *without* `user_id`. The
296
+ middleware always passes `user_id` (it's a required constructor arg),
297
+ so this agent-centric suffix is unreachable through `Mem0Middleware`
298
+ today. In practice that's fine — agent persona / configuration is
299
+ usually expressed via system prompt rather than long-term memory.
300
+
301
+ ## Service-mode integration (`agentscope.app`)
302
+
303
+ The demos above use the **library mode** — you construct `Agent`
304
+ yourself and pass `Mem0Middleware` into its `middlewares=[...]`. For
305
+ production deployments via `agentscope.app` (the FastAPI service
306
+ layer), the `user_id` already flows through the framework from the
307
+ `X-User-ID` HTTP header. Hook in through the
308
+ [`extra_agent_middlewares`](../../../../src/agentscope/app/_types.py)
309
+ factory:
310
+
311
+ ```python
312
+ from agentscope.app import create_app
313
+ from agentscope.middleware import Mem0Middleware
314
+ from agentscope.middleware._longterm_memory._mem0._agentscope_adapter \
315
+ import build_mem0_config
316
+ from mem0 import AsyncMemory
317
+
318
+ # Build the mem0 client ONCE at module scope — local OSS Qdrant
319
+ # takes an exclusive lock on its storage folder; per-request
320
+ # construction would deadlock under concurrent traffic.
321
+ chat_model = ... # shared AgentScope ChatModelBase
322
+ emb_model = ... # shared AgentScope EmbeddingModelBase
323
+ mem0_client = AsyncMemory(
324
+ config=build_mem0_config(
325
+ chat_model=chat_model,
326
+ embedding_model=emb_model,
327
+ ),
328
+ )
329
+
330
+
331
+ async def long_term_memory_factory(
332
+ user_id: str, # ← from the authenticated X-User-ID header
333
+ agent_id: str,
334
+ session_id: str,
335
+ ) -> list:
336
+ return [
337
+ Mem0Middleware(
338
+ user_id=user_id,
339
+ client=mem0_client, # shared across all requests
340
+ mode="both",
341
+ ),
342
+ ]
343
+
344
+
345
+ app = create_app(
346
+ ...,
347
+ extra_agent_middlewares=long_term_memory_factory,
348
+ )
349
+ ```
350
+
351
+ Key points:
352
+
353
+ - The factory is `async (user_id, agent_id, session_id) ->
354
+ list[MiddlewareBase]`, called **once per agent assembly**
355
+ (i.e. per chat turn / scheduled trigger). It returns fresh
356
+ `Mem0Middleware` instances each time, but they share a single
357
+ underlying mem0 client.
358
+ - `user_id` is the authenticated caller, injected by `agentscope.app`
359
+ via `get_current_user_id` (currently from `X-User-ID` header; will
360
+ become JWT-based when auth lands upstream). You forward it straight
361
+ to `Mem0Middleware(user_id=user_id, ...)` — no resolver callable
362
+ needed.
363
+ - For hosted mem0 Platform, swap the `AsyncMemory(config=...)`
364
+ construction for `AsyncMemoryClient(api_key=...)` — same factory
365
+ shape, no Qdrant lock concern.
366
+
367
+ ## Notes on the AgentScope-as-mem0-backend path
368
+
369
+ When you pass `chat_model` + `embedding_model`, the middleware
370
+ internally:
371
+
372
+ 1. Registers `AgentScopeLLM` / `AgentScopeEmbedding` in mem0's factory
373
+ dicts under provider name `"agentscope"`.
374
+ 2. Substitutes `LlmConfig` / `EmbedderConfig` with subclasses whose
375
+ validator allows `"agentscope"` (mem0's stock validator hardcodes a
376
+ whitelist that doesn't include us). Other providers continue to be
377
+ rejected with mem0's original error.
378
+ 3. Builds an `AsyncMemory` whose `.llm` and `.embedding_model` route
379
+ through the AgentScope adapters.
380
+ 4. Bridges mem0's sync API onto AgentScope's async models via a
381
+ persistent background event loop, so async clients (e.g. Ollama's
382
+ `AsyncClient`) keep their connection pool across calls.
383
+
384
+ Your embedding model's `dimensions` must match the vector store's
385
+ expected dim — mem0's default Qdrant expects 1536, which matches
386
+ DashScope's `text-embedding-v2` at `dimensions=1536` (the value used
387
+ in `oss_demo.py`).
examples/long_term_memory/mem0/oss_demo.py ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Mem0 middleware demo (open-source mem0, AgentScope-driven).
3
+
4
+ Drives two independent agent sessions for the same ``user_id`` so
5
+ mem0's cross-session memory effect is visible. Each turn streams
6
+ events from ``agent.reply_stream`` and prints the ones that matter:
7
+
8
+ - ``[mem0 → context (static)]`` — the memory note the middleware
9
+ appended to ``state.context`` (fires at ``ReplyStartEvent``, before
10
+ any tool call, so the output order matches the data flow)
11
+ - ``[tool call (agent)]`` — each ``search_memory`` / ``add_memory``
12
+ invocation the agent makes on its own
13
+ - ``[assistant]`` — the assistant's reply, concatenated from the
14
+ ``TextBlockDeltaEvent`` stream
15
+ - ``[context → mem0 (static)]`` — facts the middleware wrote back
16
+ after the turn
17
+
18
+ The mode tag (``static`` / ``agent``) on each line tells you which
19
+ control path produced it.
20
+
21
+ Starts each run from a clean mem0 store (for ``user_id``) so the
22
+ demo is reproducible.
23
+
24
+ Requires:
25
+ pip install agentscope mem0ai
26
+ export DASHSCOPE_API_KEY=sk-...
27
+ """
28
+ import asyncio
29
+ import logging
30
+ import os
31
+ import shutil
32
+
33
+ from mem0 import AsyncMemory
34
+ from mem0.configs.base import MemoryConfig
35
+ from mem0.vector_stores.configs import VectorStoreConfig
36
+
37
+ from agentscope.agent import Agent
38
+ from agentscope.credential import DashScopeCredential
39
+ from agentscope.embedding import DashScopeEmbeddingModel
40
+ from agentscope.event import (
41
+ ReplyStartEvent,
42
+ TextBlockDeltaEvent,
43
+ ToolCallDeltaEvent,
44
+ ToolCallStartEvent,
45
+ ToolResultEndEvent,
46
+ ToolResultTextDeltaEvent,
47
+ )
48
+ from agentscope.message import UserMsg
49
+ from agentscope.middleware import Mem0Middleware
50
+ from agentscope.model import DashScopeChatModel
51
+ from agentscope.tool import Toolkit
52
+
53
+
54
+ MODE = "both" # try "static_control" or "agent_control" too
55
+
56
+
57
+ # Silence mem0's noisy init warnings (qdrant migration notice, spaCy
58
+ # missing, fastembed missing) — they're informational, not actionable.
59
+ logging.getLogger("mem0").setLevel(logging.ERROR)
60
+
61
+
62
+ async def _facts_in_mem0(client: AsyncMemory, user_id: str) -> list[str]:
63
+ """Return mem0's current facts for ``user_id`` as plain strings."""
64
+ res = await client.get_all(filters={"user_id": user_id})
65
+ items = res.get("results", res) if isinstance(res, dict) else res
66
+ out: list[str] = []
67
+ for m in items:
68
+ if isinstance(m, dict):
69
+ text = m.get("memory")
70
+ if isinstance(text, str) and text:
71
+ out.append(text)
72
+ return out
73
+
74
+
75
+ def _injected_memory_bullets(agent: Agent) -> list[str]:
76
+ """Extract the bullet lines from the memory note the middleware
77
+ appended to ``agent.state.context`` (if any) — strips the section
78
+ header and intro so we see only the actual retrieved facts."""
79
+ for msg in agent.state.context:
80
+ if getattr(msg, "name", None) != "memory":
81
+ continue
82
+ hint_text = "\n".join(
83
+ block.hint for block in msg.get_content_blocks("hint")
84
+ )
85
+ return [
86
+ line[2:].strip()
87
+ for line in hint_text.splitlines()
88
+ if line.startswith("- ")
89
+ ]
90
+ return []
91
+
92
+
93
+ async def _run_turn(agent: Agent, user_msg: UserMsg) -> str:
94
+ """Drive one reply turn through ``agent.reply_stream`` and print
95
+ each middleware contribution as it happens, in the order it
96
+ happens:
97
+
98
+ 1. ``ReplyStartEvent`` fires right after the agent ingests the new
99
+ user input AND the middleware has appended any retrieved memory
100
+ note to ``state.context`` (static path) — so this is the right
101
+ place to surface ``[mem0 → context]``.
102
+ 2. ``ToolCallStartEvent`` / ``ToolResultEndEvent`` bracket each
103
+ ``search_memory`` / ``add_memory`` invocation the agent makes
104
+ on its own (agent path).
105
+ 3. ``TextBlockDeltaEvent`` carries the assistant's streamed reply
106
+ text — concatenating every delta yields the final message
107
+ content (there is no separate "final Msg" reply_stream
108
+ withholds).
109
+
110
+ Each printed line is tagged with the mem0 control path that
111
+ produced it (``static`` vs ``agent``) so the demo stays readable
112
+ in any of the three modes.
113
+ """
114
+ pending_args: dict[str, str] = {}
115
+ pending_names: dict[str, str] = {}
116
+ pending_results: dict[str, str] = {}
117
+ text_parts: list[str] = []
118
+ memory_announced = False
119
+
120
+ async for ev in agent.reply_stream(inputs=user_msg):
121
+ if isinstance(ev, ReplyStartEvent) and not memory_announced:
122
+ injected = _injected_memory_bullets(agent)
123
+ print(
124
+ f"[mem0 → context (static)] retrieved "
125
+ f"{len(injected)} memory note(s):",
126
+ )
127
+ for b in injected:
128
+ print(f" ← {b}")
129
+ memory_announced = True
130
+ elif isinstance(ev, ToolCallStartEvent):
131
+ pending_names[ev.tool_call_id] = ev.tool_call_name
132
+ pending_args[ev.tool_call_id] = ""
133
+ pending_results[ev.tool_call_id] = ""
134
+ elif isinstance(ev, ToolCallDeltaEvent):
135
+ pending_args[ev.tool_call_id] += ev.delta
136
+ elif isinstance(ev, ToolResultTextDeltaEvent):
137
+ pending_results[ev.tool_call_id] += ev.delta
138
+ elif isinstance(ev, ToolResultEndEvent):
139
+ name = pending_names.pop(ev.tool_call_id, "<unknown>")
140
+ args = pending_args.pop(ev.tool_call_id, "")
141
+ result = pending_results.pop(ev.tool_call_id, "")
142
+ # ev.state is a StrEnum at the type level but pydantic
143
+ # may have deserialized it to a plain str; f-string both
144
+ # cases yields the same value string.
145
+ print(f"[tool call (agent)] {name}({args}) → state={ev.state}")
146
+ for line in result.splitlines() or [""]:
147
+ if line:
148
+ print(f" → {line}")
149
+ elif isinstance(ev, TextBlockDeltaEvent):
150
+ text_parts.append(ev.delta)
151
+
152
+ return "".join(text_parts)
153
+
154
+
155
+ async def main() -> None:
156
+ """Drive two cross-session agent turns and print middleware effects."""
157
+ api_key = os.environ["DASHSCOPE_API_KEY"]
158
+ user_id = "alice"
159
+
160
+ # Wipe mem0's local files BEFORE constructing the client so each
161
+ # demo run starts clean. We don't use ``mem0_client.delete_all()``
162
+ # because qdrant-client's local SQLite layer has a race under
163
+ # mem0's parallel ``asyncio.gather`` deletes (sqlite3.InterfaceError).
164
+ qdrant_path = "/tmp/qdrant" # matches the vector_store config below
165
+ history_db = os.path.expanduser("~/.mem0/history.db")
166
+ print("=== resetting mem0 local state ===")
167
+ print(f" rm -rf {qdrant_path}")
168
+ shutil.rmtree(qdrant_path, ignore_errors=True)
169
+ print(f" rm -f {history_db}")
170
+ try:
171
+ os.remove(history_db)
172
+ except FileNotFoundError:
173
+ pass
174
+
175
+ # `stream` can be True or False — the AgentScope→mem0 adapter
176
+ # drains an async generator and uses the last chunk (which carries
177
+ # the full accumulated content per AgentScope's streaming contract).
178
+ # The agent's own `reply_stream` still emits per-delta events
179
+ # regardless of this setting, so streaming-mode does not change the
180
+ # demo's printed output shape.
181
+ chat_model = DashScopeChatModel(
182
+ credential=DashScopeCredential(api_key=api_key),
183
+ model="qwen3.7-max",
184
+ stream=False,
185
+ )
186
+ embedding_model = DashScopeEmbeddingModel(
187
+ credential=DashScopeCredential(api_key=api_key),
188
+ model="text-embedding-v4",
189
+ dimensions=1536, # matches mem0's Qdrant default
190
+ )
191
+
192
+ # Explicit vector-store config (here we just spell out mem0's
193
+ # default local Qdrant — collection ``mem0``, on-disk at
194
+ # ``/tmp/qdrant``, 1536-d vectors). Override any of these for
195
+ # remote Qdrant, alternate collection names, etc. Pass the
196
+ # ``MemoryConfig`` through ``mem0_config=`` and the middleware
197
+ # keeps everything you set, only swapping ``.llm`` and
198
+ # ``.embedder`` to route through your AgentScope models.
199
+ #
200
+ # Recommended for Windows users and any production setup: run
201
+ # Qdrant in Docker and connect over the network instead of using
202
+ # the local on-disk backend. Local on-disk Qdrant takes an
203
+ # exclusive file lock that's brittle on Windows (different
204
+ # filesystem-lock semantics) and breaks under concurrent agent
205
+ # instances. To switch:
206
+ #
207
+ # docker run -p 6333:6333 -p 6334:6334 \\
208
+ # -v $(pwd)/qdrant_storage:/qdrant/storage \\
209
+ # qdrant/qdrant
210
+ #
211
+ # vector_store=VectorStoreConfig(
212
+ # provider="qdrant",
213
+ # config={
214
+ # "collection_name": "mem0",
215
+ # "host": "localhost", # Docker container
216
+ # "port": 6333,
217
+ # "embedding_model_dims": 1536,
218
+ # },
219
+ # )
220
+ #
221
+ # (You'd also drop the ``shutil.rmtree(qdrant_path)`` wipe above —
222
+ # state lives in the Docker volume, not the local filesystem.)
223
+ mem0_cfg = MemoryConfig(
224
+ vector_store=VectorStoreConfig(
225
+ provider="qdrant",
226
+ config={
227
+ "collection_name": "mem0",
228
+ "path": qdrant_path,
229
+ "embedding_model_dims": 1536,
230
+ "on_disk": False,
231
+ },
232
+ ),
233
+ )
234
+
235
+ mw = Mem0Middleware(
236
+ user_id=user_id,
237
+ agent_id="datascope_assistant",
238
+ chat_model=chat_model,
239
+ embedding_model=embedding_model,
240
+ mem0_config=mem0_cfg,
241
+ mode=MODE,
242
+ top_k=5,
243
+ )
244
+ # For the hosted mem0 Platform, swap the construction above for:
245
+ #
246
+ # from mem0 import AsyncMemoryClient
247
+ # mw = Mem0Middleware(
248
+ # user_id=user_id,
249
+ # client=AsyncMemoryClient(api_key=os.environ["MEM0_API_KEY"]),
250
+ # mode=MODE,
251
+ # )
252
+ #
253
+ # No local Qdrant / vector_store config needed — extraction and
254
+ # storage all happen in mem0's cloud service.
255
+ # pylint: disable-next=protected-access
256
+ mem0_client: AsyncMemory = mw._client # demo only — peek at the
257
+ # constructed client to inspect mem0 state between turns.
258
+
259
+ # =================================================================
260
+ # SESSION 1
261
+ # =================================================================
262
+ print(f"\n=== SESSION 1 (mode={MODE!r}) ===")
263
+ user_msg_1 = (
264
+ "Hi! For any chart, please default to dark mode and use "
265
+ "matplotlib. Also I'm based in Hangzhou."
266
+ )
267
+ print(f"\n[user] {user_msg_1}\n")
268
+
269
+ before = await _facts_in_mem0(mem0_client, user_id)
270
+
271
+ agent = Agent(
272
+ name="datascope_assistant",
273
+ system_prompt=(
274
+ "You are a helpful data-analysis assistant. Be concise. "
275
+ "If you learn a durable user preference, save it with "
276
+ "the add_memory tool when one is available."
277
+ ),
278
+ model=chat_model,
279
+ toolkit=Toolkit(tools=await mw.list_tools()),
280
+ middlewares=[mw],
281
+ )
282
+ reply_text = await _run_turn(agent, UserMsg("alice", user_msg_1))
283
+ print(f"\n[assistant] {reply_text}")
284
+
285
+ after = await _facts_in_mem0(mem0_client, user_id)
286
+ new = [f for f in after if f not in before]
287
+ print(f"\n[context → mem0 (static)] extracted {len(new)} new fact(s):")
288
+ for f in new:
289
+ print(f" + {f}")
290
+
291
+ # =================================================================
292
+ # SESSION 2 — fresh Agent, empty chat context. mem0 should bridge.
293
+ # =================================================================
294
+ print(f"\n=== SESSION 2 (fresh agent, mem0 bridges; mode={MODE!r}) ===")
295
+ user_msg_2 = (
296
+ "Plot me a bar chart of monthly sales — pick reasonable "
297
+ "defaults for theme and library."
298
+ )
299
+ print(f"\n[user] {user_msg_2}\n")
300
+
301
+ before = await _facts_in_mem0(mem0_client, user_id)
302
+
303
+ agent = Agent(
304
+ name="datascope_assistant",
305
+ system_prompt=(
306
+ "You are a helpful data-analysis assistant. Be concise. "
307
+ "If you learn a durable user preference, save it with "
308
+ "the add_memory tool when one is available."
309
+ ),
310
+ model=chat_model,
311
+ toolkit=Toolkit(tools=await mw.list_tools()),
312
+ middlewares=[mw],
313
+ )
314
+ reply_text = await _run_turn(agent, UserMsg("alice", user_msg_2))
315
+ print(f"\n[assistant] {reply_text}")
316
+
317
+ after = await _facts_in_mem0(mem0_client, user_id)
318
+ new = [f for f in after if f not in before]
319
+ print(f"\n[context → mem0 (static)] extracted {len(new)} new fact(s):")
320
+ for f in new:
321
+ print(f" + {f}")
322
+
323
+
324
+ if __name__ == "__main__":
325
+ asyncio.run(main())
examples/rag/README.md ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # RAG Examples
2
+
3
+ Two library-mode walk-throughs of `agentscope.rag` — no FastAPI service, no manager, no message bus. Each script wires the building blocks (parser, chunker, embedding model, vector store, `KnowledgeBase` handle) by hand so the data flow is visible end-to-end.
4
+
5
+ | Script | What it shows |
6
+ | --- | --- |
7
+ | [`index_and_search.py`](./index_and_search.py) | The minimal pipeline: parse → chunk → embed → insert, then `KnowledgeBase.search`. Start here. |
8
+ | [`integrate_with_agent.py`](./integrate_with_agent.py) | Attaches the same `KnowledgeBase` to an `Agent` via `RAGMiddleware`, in both `static` (auto-inject) and `agentic` (tool-driven) modes. |
9
+
10
+ Both examples use an in-memory Qdrant store (`location=":memory:"`) and the DashScope `text-embedding-v4` model, so no external services are required.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ # From PyPI
16
+ uv pip install "agentscope[rag]"
17
+
18
+ # Or from source (repo root)
19
+ uv pip install -e ".[rag]"
20
+ ```
21
+
22
+ `integrate_with_agent.py` additionally uses `DashScopeChatModel`, which is already in the base `agentscope` dependencies.
23
+
24
+ ## Run
25
+
26
+ ```bash
27
+ export DASHSCOPE_API_KEY=sk-...
28
+
29
+ python examples/rag/index_and_search.py
30
+ python examples/rag/integrate_with_agent.py
31
+ ```
32
+
33
+ ## Service mode
34
+
35
+ The two scripts above are library-mode — you drive the pipeline yourself in a single process. For the full service-mode experience (FastAPI endpoints for knowledge base CRUD, document upload, indexing workers, and search), see [`examples/agent_service`](../agent_service) for the backend and [`examples/web_ui`](../web_ui) for the chat-style UI.
36
+
examples/rag/index_and_search.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Library-mode RAG walk-through — no FastAPI service, no manager.
3
+
4
+ End-to-end demo of the building blocks in :mod:`agentscope.rag`:
5
+
6
+ 1. **Vector store** — :class:`QdrantStore` (in-memory here; swap to
7
+ ``url=...`` for a real Qdrant server).
8
+ 2. **Parser** — :class:`TextParser` turning raw bytes into ``Section``
9
+ objects.
10
+ 3. **Chunker** — :class:`ApproxTokenChunker` splitting sections into
11
+ ``Chunk`` objects (the indexable unit).
12
+ 4. **Embedding** — any :class:`EmbeddingModelBase` subclass; we use the
13
+ DashScope text-embedding model.
14
+ 5. **KnowledgeBase** — the runtime handle that ties (embedding, vector
15
+ store, collection) together and exposes
16
+ ``insert_document`` / ``search`` / ``list_documents`` /
17
+ ``delete_document``. This is what :class:`RAGMiddleware` consumes.
18
+
19
+ The pipeline:
20
+
21
+ bytes ── parser ──► Section[] ── chunker ──► Chunk[]
22
+
23
+
24
+ knowledge.insert_document(chunks)
25
+
26
+
27
+ embed → store on the bound collection
28
+
29
+ Search mirrors the same shape: hand the query to
30
+ ``knowledge.search(...)``, get ``VectorSearchResult`` back.
31
+
32
+ Run with::
33
+
34
+ DASHSCOPE_API_KEY=sk-... python examples/rag/index_and_search.py
35
+ """
36
+ import asyncio
37
+ import os
38
+
39
+ from agentscope.credential import DashScopeCredential
40
+ from agentscope.embedding import DashScopeEmbeddingModel
41
+ from agentscope.message import TextBlock
42
+ from agentscope.rag import (
43
+ ApproxTokenChunker,
44
+ KnowledgeBase,
45
+ QdrantStore,
46
+ TextParser,
47
+ )
48
+
49
+
50
+ COLLECTION = "demo-kb"
51
+
52
+ # A toy corpus inlined as bytes so the example has no on-disk
53
+ # dependencies. In real use these would come from uploaded files or
54
+ # blob-store reads.
55
+ DOCUMENTS: dict[str, bytes] = {
56
+ "cats.md": (
57
+ b"# Cats\n\n"
58
+ b"Cats are small carnivorous mammals. They are popular as pets "
59
+ b"because of their playful and affectionate nature.\n\n"
60
+ b"Domestic cats sleep around 12-16 hours per day. They are most "
61
+ b"active at dawn and dusk (crepuscular behaviour).\n"
62
+ ),
63
+ "agentscope.md": (
64
+ b"# AgentScope\n\n"
65
+ b"AgentScope is a developer-centric framework for building "
66
+ b"multi-agent LLM applications. It emphasises transparency, "
67
+ b"controllability, and a clear separation between agent logic "
68
+ b"and infrastructure.\n\n"
69
+ b"Its RAG module ships a parser/chunker/embedding/vector-store "
70
+ b"pipeline that can be wired up without the FastAPI service.\n"
71
+ ),
72
+ }
73
+
74
+
75
+ async def build_index(
76
+ knowledge: KnowledgeBase,
77
+ parser: TextParser,
78
+ chunker: ApproxTokenChunker,
79
+ ) -> None:
80
+ """Parse → chunk → insert for every demo document.
81
+
82
+ Embedding and vector-store insertion are encapsulated inside
83
+ ``knowledge.insert_document`` — caller side only has to bring
84
+ pre-chunked content.
85
+ """
86
+ for filename, file_bytes in DOCUMENTS.items():
87
+ # 1. Parse: bytes → list[Section]
88
+ sections = await parser.parse(file=file_bytes, filename=filename)
89
+
90
+ # 2. Chunk: list[Section] → list[Chunk]
91
+ chunks = await chunker.chunk(sections)
92
+
93
+ # 3. Insert: embeds every chunk under one document id. The
94
+ # returned id is yours to keep for later
95
+ # ``delete_document``.
96
+ document_id = await knowledge.insert_document(
97
+ chunks,
98
+ document_metadata={"filename": filename},
99
+ )
100
+ print(
101
+ f" indexed {filename!r} as document_id={document_id} "
102
+ f"({len(chunks)} chunk(s))",
103
+ )
104
+
105
+
106
+ async def search(
107
+ knowledge: KnowledgeBase,
108
+ query: str,
109
+ top_k: int = 3,
110
+ ) -> None:
111
+ """Run a search via the :class:`KnowledgeBase` handle and print hits."""
112
+ results = await knowledge.search(queries=[query], top_k=top_k)
113
+
114
+ print(f"\nQuery: {query!r}")
115
+ if not results:
116
+ print(" (no hits)")
117
+ return
118
+ for rank, result in enumerate(results, start=1):
119
+ # Only text chunks are printable as-is.
120
+ snippet = (
121
+ result.chunk.content.text
122
+ if isinstance(result.chunk.content, TextBlock)
123
+ else "<non-text chunk>"
124
+ )
125
+ snippet = snippet.replace("\n", " ").strip()
126
+ if len(snippet) > 120:
127
+ snippet = snippet[:117] + "..."
128
+ print(
129
+ f" [{rank}] score={result.score:.4f} "
130
+ f"source={result.chunk.source} "
131
+ f"document_id={result.document_id}\n"
132
+ f" {snippet}",
133
+ )
134
+
135
+
136
+ async def main() -> None:
137
+ """The main entry point of the example."""
138
+ api_key = os.environ.get("DASHSCOPE_API_KEY")
139
+ if not api_key:
140
+ raise RuntimeError(
141
+ "Set DASHSCOPE_API_KEY before running this example.",
142
+ )
143
+
144
+ # The building blocks. All of these are also what the service-mode
145
+ # (``create_app``) wiring uses internally — the only difference is
146
+ # that here you drive them yourself.
147
+ embedding_model = DashScopeEmbeddingModel(
148
+ credential=DashScopeCredential(api_key=api_key),
149
+ model="text-embedding-v4",
150
+ dimensions=1024,
151
+ )
152
+ parser = TextParser()
153
+ chunker = ApproxTokenChunker(chunk_size=256, overlap=32)
154
+ store = QdrantStore(location=":memory:")
155
+
156
+ # ``QdrantStore`` is an async context manager — entering it opens
157
+ # the client connection; exiting closes it.
158
+ async with store:
159
+ # One :class:`KnowledgeBase` handle bundles (embedding, vector
160
+ # store, collection) together so the rest of this example
161
+ # never has to repeat the wiring. The collection is created
162
+ # lazily on the first operation (`build_index`).
163
+ knowledge = KnowledgeBase(
164
+ name="demo-kb",
165
+ description="A toy corpus on cats and AgentScope.",
166
+ embedding_model=embedding_model,
167
+ vector_store=store,
168
+ collection=COLLECTION,
169
+ )
170
+
171
+ print("Indexing demo corpus ...")
172
+ await build_index(knowledge, parser, chunker)
173
+
174
+ # A couple of search queries that demonstrate scoring.
175
+ await search(knowledge, "When are cats most active?")
176
+ await search(
177
+ knowledge,
178
+ "What framework lets me build multi-agent apps?",
179
+ )
180
+
181
+
182
+ if __name__ == "__main__":
183
+ asyncio.run(main())
examples/rag/integrate_with_agent.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Wire :class:`RAGMiddleware` into an :class:`Agent` — library mode.
3
+
4
+ The middleware in :mod:`agentscope.middleware._rag` is the agent-side
5
+ half of RAG: given one or more :class:`~agentscope.rag.KnowledgeBase`
6
+ handles (each pairing an embedding model with a vector-store
7
+ collection), the middleware drives search on each new user turn and
8
+ feeds the matched chunks into the model context.
9
+
10
+ This example reuses the indexing pipeline shown in ``index_and_search.py``
11
+ (parse → chunk → embed → insert) and then attaches the same knowledge base
12
+ to two agents — one per mode:
13
+
14
+ - ``"static"``: on the first reasoning step of a reply, embed the
15
+ user's question, search, and inject the top hits as a one-shot
16
+ :class:`HintBlock` into the agent's context. The model sees the
17
+ matched snippets but never "decides" to search.
18
+ - ``"agentic"`` (the default): expose a ``search_knowledge`` tool. The
19
+ model decides when (and what) to search, the same way it decides any
20
+ other tool call.
21
+
22
+ Run with::
23
+
24
+ DASHSCOPE_API_KEY=sk-... python examples/rag/integrate_with_agent.py
25
+ """
26
+ import asyncio
27
+ import os
28
+
29
+ from agentscope.agent import Agent
30
+ from agentscope.credential import DashScopeCredential
31
+ from agentscope.embedding import DashScopeEmbeddingModel
32
+ from agentscope.message import UserMsg
33
+ from agentscope.middleware import RAGMiddleware
34
+ from agentscope.model import DashScopeChatModel
35
+ from agentscope.rag import (
36
+ ApproxTokenChunker,
37
+ KnowledgeBase,
38
+ QdrantStore,
39
+ TextParser,
40
+ )
41
+ from agentscope.tool import Toolkit
42
+
43
+
44
+ COLLECTION = "demo-kb"
45
+
46
+ KNOWLEDGE: dict[str, bytes] = {
47
+ "company-policy.md": (
48
+ b"# Acme Remote Work Policy\n\n"
49
+ b"Employees may work remotely up to three days per week. "
50
+ b"Wednesdays are mandatory in-office days for the whole "
51
+ b"engineering org so cross-team syncs land on a predictable "
52
+ b"day.\n\n"
53
+ b"Equipment stipend: each new hire receives a USD 1,500 "
54
+ b"one-off stipend for a home-office setup. Receipts must be "
55
+ b"submitted within 90 days of the start date.\n"
56
+ ),
57
+ "release-notes.md": (
58
+ b"# AgentScope 3.0 release notes\n\n"
59
+ b"- New ``agentscope.rag`` module: pluggable parser, chunker, "
60
+ b"embedding, and vector-store backends.\n"
61
+ b"- ``RAGMiddleware`` ships in two modes -- ``static`` for "
62
+ b"automatic injection, ``agentic`` for tool-driven search.\n"
63
+ b"- Knowledge base service supports embedded and dedicated "
64
+ b"worker deployments through a single message-bus channel.\n"
65
+ ),
66
+ }
67
+
68
+
69
+ async def index_corpus(knowledge: KnowledgeBase) -> None:
70
+ """Index the demo corpus into the knowledge base.
71
+
72
+ Identical pipeline to ``examples/rag/index_and_search.py`` — extracted
73
+ as a helper here so the agent-side wiring stays the focus. Each source
74
+ file becomes one logical document; ``KnowledgeBase.insert_document``
75
+ embeds and inserts every chunk in a single batch.
76
+ """
77
+ parser = TextParser()
78
+ chunker = ApproxTokenChunker(chunk_size=256, overlap=32)
79
+ for filename, file_bytes in KNOWLEDGE.items():
80
+ sections = await parser.parse(file=file_bytes, filename=filename)
81
+ chunks = await chunker.chunk(sections)
82
+ await knowledge.insert_document(
83
+ chunks,
84
+ document_metadata={"filename": filename},
85
+ )
86
+
87
+
88
+ def build_agent(
89
+ name: str,
90
+ *,
91
+ chat_model: DashScopeChatModel,
92
+ rag_mw: RAGMiddleware,
93
+ ) -> Agent:
94
+ """Construct an :class:`Agent` with the RAG middleware attached.
95
+
96
+ The middleware is just one entry in the ``middlewares=`` list; it
97
+ composes with every other middleware (tool offload, mem0, ...) the
98
+ agent uses.
99
+ """
100
+ return Agent(
101
+ name=name,
102
+ system_prompt=(
103
+ "You are a concise assistant. Use matched context when "
104
+ "available; if you don't know, say so."
105
+ ),
106
+ model=chat_model,
107
+ toolkit=Toolkit(),
108
+ middlewares=[rag_mw],
109
+ )
110
+
111
+
112
+ async def ask(agent: Agent, question: str) -> None:
113
+ """Run one reply and print the final assistant message."""
114
+ print(f"\n[{agent.name}] user: {question}")
115
+ reply = await agent.reply(UserMsg(name="user", content=question))
116
+ print(f"[{agent.name}] assistant: {reply.get_text_content()}")
117
+
118
+
119
+ async def main() -> None:
120
+ """The main entry point of the example."""
121
+ api_key = os.environ.get("DASHSCOPE_API_KEY")
122
+ if not api_key:
123
+ raise RuntimeError(
124
+ "Set DASHSCOPE_API_KEY before running this example.",
125
+ )
126
+
127
+ credential = DashScopeCredential(api_key=api_key)
128
+ chat_model = DashScopeChatModel(
129
+ credential=credential,
130
+ model="qwen-plus",
131
+ stream=False,
132
+ )
133
+ embedding_model = DashScopeEmbeddingModel(
134
+ credential=credential,
135
+ model="text-embedding-v4",
136
+ dimensions=1024,
137
+ )
138
+
139
+ store = QdrantStore(location=":memory:")
140
+ async with store:
141
+ # One :class:`KnowledgeBase` handle binds embedding + vector store +
142
+ # collection together. ``insert_document`` / ``search`` /
143
+ # ``list_documents`` all go through it, and the backing
144
+ # collection is created lazily on first use.
145
+ knowledge = KnowledgeBase(
146
+ name="acme-handbook",
147
+ description="Acme HR policies and AgentScope 3.0 release notes.",
148
+ embedding_model=embedding_model,
149
+ vector_store=store,
150
+ collection=COLLECTION,
151
+ )
152
+
153
+ await index_corpus(knowledge)
154
+
155
+ # ---- Mode 1: static ----
156
+ # Search is automatic on the first reasoning step. The injected
157
+ # ``HintBlock`` is one-shot (removed after the model call) so it
158
+ # doesn't poison the next turn.
159
+ static_mw = RAGMiddleware(
160
+ knowledge_bases=[knowledge],
161
+ parameters=RAGMiddleware.Parameters(
162
+ mode="static",
163
+ top_k=3,
164
+ emit_hint_event=False,
165
+ ),
166
+ )
167
+ static_agent = build_agent(
168
+ "rag-static-agent",
169
+ chat_model=chat_model,
170
+ rag_mw=static_mw,
171
+ )
172
+ await ask(
173
+ static_agent,
174
+ "How many remote days per week does Acme allow?",
175
+ )
176
+
177
+ # ---- Mode 2: agentic ----
178
+ # The middleware exposes a ``search_knowledge`` tool instead of
179
+ # auto-injecting. The model decides when to call it; it may
180
+ # also pass ``knowledge_bases=[...]`` to scope the search when
181
+ # multiple knowledge bases are bound.
182
+ agentic_mw = RAGMiddleware(
183
+ knowledge_bases=[knowledge],
184
+ parameters=RAGMiddleware.Parameters(mode="agentic", top_k=3),
185
+ )
186
+ agentic_agent = build_agent(
187
+ "rag-agentic-agent",
188
+ chat_model=chat_model,
189
+ rag_mw=agentic_mw,
190
+ )
191
+ await ask(
192
+ agentic_agent,
193
+ "Summarise what's new in the AgentScope 3.0 release notes.",
194
+ )
195
+
196
+
197
+ if __name__ == "__main__":
198
+ asyncio.run(main())
examples/web_ui/.gitignore ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /tmp
2
+ /out-tsc
3
+
4
+ node_modules
5
+ npm-debug.log*
6
+ yarn-debug.log*
7
+ yarn-error.log*
8
+ /.pnp
9
+ .pnp.js
10
+
11
+ .vscode/*
12
+
13
+ # OS files
14
+ .DS_Store
15
+
16
+ dist
17
+ build
18
+ out
19
+ .eslintcache
20
+ *.log*
21
+
22
+ .env
23
+ .env.*
24
+ __pycache__/
25
+ *.db
26
+ *.sqlite
27
+ *.sqlite3
28
+
29
+ .idea/
30
+ .vscode
31
+ *.suo
32
+ *.ntvs*
33
+ *.njsproj
34
+ *.sln
35
+ *.sw?
36
+
37
+ # Mintlify
38
+ docs/.mintlify
39
+ docs/node_modules
40
+ docs/.next
41
+ docs/.cache
42
+ docs/api-reference/generated
examples/web_ui/.husky/pre-commit ADDED
@@ -0,0 +1 @@
 
 
1
+ npx lint-staged
examples/web_ui/.prettierignore ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dependencies
2
+ node_modules
3
+ pnpm-lock.yaml
4
+
5
+ # Build outputs
6
+ dist
7
+ build
8
+ .next
9
+ out
10
+
11
+ # Cache
12
+ .cache
13
+ .turbo
14
+ .vite
15
+
16
+ # Logs
17
+ *.log
18
+ logs
19
+
20
+ # Environment
21
+ .env
22
+ .env.*
23
+
24
+ # IDE
25
+ .vscode
26
+ .idea
27
+
28
+ # OS
29
+ .DS_Store
30
+ Thumbs.db
31
+
32
+ # Generated files
33
+ coverage
34
+ *.min.js
35
+ *.min.css
36
+
37
+ # Claude
38
+ .claude
examples/web_ui/.prettierrc ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "useTabs": true,
3
+ "tabWidth": 4,
4
+ "singleQuote": true,
5
+ "semi": true,
6
+ "trailingComma": "all",
7
+ "printWidth": 100,
8
+ "bracketSpacing": true,
9
+ "arrowParens": "always"
10
+ }
examples/web_ui/backend/package.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "backend",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "nodemon --watch src --ext ts --exec ts-node src/index.ts",
7
+ "build": "tsc",
8
+ "start": "node dist/index.js"
9
+ },
10
+ "dependencies": {
11
+ "express": "^4.19.2",
12
+ "cors": "^2.8.5"
13
+ },
14
+ "devDependencies": {
15
+ "@types/express": "^4.17.21",
16
+ "@types/cors": "^2.8.17",
17
+ "@types/node": "^20.14.0",
18
+ "nodemon": "^3.1.4",
19
+ "ts-node": "^10.9.2",
20
+ "typescript": "^5.5.2"
21
+ }
22
+ }
examples/web_ui/backend/src/index.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+
4
+ const app = express();
5
+ const PORT = process.env.PORT || 3000;
6
+
7
+ app.use(cors());
8
+ app.use(express.json());
9
+
10
+ app.get('/api/health', (_req, res) => {
11
+ res.json({ status: 'ok' });
12
+ });
13
+
14
+ app.listen(PORT, () => {
15
+ console.log(`Server running on http://localhost:${PORT}`);
16
+ });
examples/web_ui/backend/tsconfig.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true
13
+ },
14
+ "include": ["src"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }
examples/web_ui/frontend/README.md ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # React + TypeScript + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17
+
18
+ ```js
19
+ export default defineConfig([
20
+ globalIgnores(['dist']),
21
+ {
22
+ files: ['**/*.{ts,tsx}'],
23
+ extends: [
24
+ // Other configs...
25
+
26
+ // Remove tseslint.configs.recommended and replace with this
27
+ tseslint.configs.recommendedTypeChecked,
28
+ // Alternatively, use this for stricter rules
29
+ tseslint.configs.strictTypeChecked,
30
+ // Optionally, add this for stylistic rules
31
+ tseslint.configs.stylisticTypeChecked,
32
+
33
+ // Other configs...
34
+ ],
35
+ languageOptions: {
36
+ parserOptions: {
37
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
+ tsconfigRootDir: import.meta.dirname,
39
+ },
40
+ // other options...
41
+ },
42
+ },
43
+ ]);
44
+ ```
45
+
46
+ You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47
+
48
+ ```js
49
+ // eslint.config.js
50
+ import reactX from 'eslint-plugin-react-x';
51
+ import reactDom from 'eslint-plugin-react-dom';
52
+
53
+ export default defineConfig([
54
+ globalIgnores(['dist']),
55
+ {
56
+ files: ['**/*.{ts,tsx}'],
57
+ extends: [
58
+ // Other configs...
59
+ // Enable lint rules for React
60
+ reactX.configs['recommended-typescript'],
61
+ // Enable lint rules for React DOM
62
+ reactDom.configs.recommended,
63
+ ],
64
+ languageOptions: {
65
+ parserOptions: {
66
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
67
+ tsconfigRootDir: import.meta.dirname,
68
+ },
69
+ // other options...
70
+ },
71
+ },
72
+ ]);
73
+ ```
examples/web_ui/frontend/components.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "radix-nova",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/index.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "rtl": false,
15
+ "aliases": {
16
+ "components": "@/components",
17
+ "utils": "@/lib/utils",
18
+ "ui": "@/components/ui",
19
+ "lib": "@/lib",
20
+ "hooks": "@/hooks"
21
+ },
22
+ "menuColor": "default",
23
+ "menuAccent": "subtle",
24
+ "registries": {}
25
+ }
examples/web_ui/frontend/eslint.config.js ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js';
2
+ import globals from 'globals';
3
+ import importX from 'eslint-plugin-import-x';
4
+ import reactHooks from 'eslint-plugin-react-hooks';
5
+ import reactRefresh from 'eslint-plugin-react-refresh';
6
+ import tseslint from 'typescript-eslint';
7
+ import { defineConfig, globalIgnores } from 'eslint/config';
8
+
9
+ export default defineConfig([
10
+ globalIgnores(['dist']),
11
+ {
12
+ files: ['**/*.{ts,tsx}'],
13
+ plugins: {
14
+ 'import-x': importX,
15
+ },
16
+ extends: [
17
+ js.configs.recommended,
18
+ tseslint.configs.recommended,
19
+ reactHooks.configs.flat.recommended,
20
+ reactRefresh.configs.vite,
21
+ ],
22
+ languageOptions: {
23
+ globals: globals.browser,
24
+ },
25
+ rules: {
26
+ 'import-x/order': [
27
+ 'error',
28
+ {
29
+ groups: ['builtin', 'external', 'internal', 'index'],
30
+ 'newlines-between': 'always',
31
+ alphabetize: {
32
+ order: 'asc',
33
+ caseInsensitive: true,
34
+ },
35
+ },
36
+ ],
37
+ 'react-hooks/set-state-in-effect': 'off',
38
+ 'react-refresh/only-export-components': 'warn',
39
+ '@typescript-eslint/no-unused-expressions': 'off',
40
+ '@typescript-eslint/only-throw-error': 'off',
41
+ 'preserve-caught-error': 'off',
42
+ },
43
+ },
44
+ {
45
+ files: ['**/components/ui/**/*.{ts,tsx}'],
46
+ rules: {
47
+ '@typescript-eslint/no-explicit-any': 'off',
48
+ '@typescript-eslint/no-unused-vars': 'off',
49
+ 'react-refresh/only-export-components': 'off',
50
+ 'import-x/order': 'off',
51
+ },
52
+ },
53
+ ]);
examples/web_ui/frontend/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/agentscope.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>AgentScope</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
examples/web_ui/frontend/package.json ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "eslint .",
10
+ "lint:fix": "eslint . --fix",
11
+ "preview": "vite preview"
12
+ },
13
+ "dependencies": {
14
+ "@agentscope-ai/agentscope": "^0.0.12",
15
+ "@fontsource-variable/geist": "^5.2.8",
16
+ "@tailwindcss/vite": "^4.3.0",
17
+ "class-variance-authority": "^0.7.1",
18
+ "clsx": "^2.1.1",
19
+ "cron-parser": "^5.5.0",
20
+ "date-fns": "^4.1.0",
21
+ "framer-motion": "^12.40.0",
22
+ "i18next": "^26.2.0",
23
+ "i18next-browser-languagedetector": "^8.2.1",
24
+ "lucide": "^1.3.0",
25
+ "lucide-react": "^1.16.0",
26
+ "mime-types": "^3.0.2",
27
+ "next-themes": "^0.4.6",
28
+ "onborda": "^1.2.5",
29
+ "radix-ui": "^1.4.3",
30
+ "react": "^19.2.6",
31
+ "react-day-picker": "^10.0.1",
32
+ "react-diff-view": "^3.3.3",
33
+ "react-dom": "^19.2.6",
34
+ "react-i18next": "^17.0.8",
35
+ "react-markdown": "^10.1.0",
36
+ "react-resizable-panels": "^4.11.2",
37
+ "react-router-dom": "^7.15.1",
38
+ "remark-gfm": "^4.0.1",
39
+ "shadcn": "^4.7.0",
40
+ "shadcn-prose": "^1.2.1",
41
+ "sonner": "^2.0.7",
42
+ "tailwind-merge": "^3.6.0",
43
+ "tailwindcss": "^4.3.0",
44
+ "tw-animate-css": "^1.4.0",
45
+ "unidiff": "^1.0.4",
46
+ "vaul": "^1.1.2"
47
+ },
48
+ "devDependencies": {
49
+ "@eslint/js": "^10.0.1",
50
+ "@tailwindcss/typography": "^0.5.19",
51
+ "@types/mime-types": "^3.0.1",
52
+ "@types/node": "^24.12.3",
53
+ "@types/react": "^19.2.14",
54
+ "@types/react-dom": "^19.2.3",
55
+ "@vitejs/plugin-react": "^6.0.1",
56
+ "eslint": "^10.3.0",
57
+ "eslint-plugin-import-x": "^4.16.2",
58
+ "eslint-plugin-react-hooks": "^7.1.1",
59
+ "eslint-plugin-react-refresh": "^0.5.2",
60
+ "globals": "^17.6.0",
61
+ "typescript": "~6.0.2",
62
+ "typescript-eslint": "^8.59.2",
63
+ "vite": "^8.0.12",
64
+ "vite-plugin-svgr": "^5.2.0"
65
+ }
66
+ }
examples/web_ui/frontend/public/agentscope.svg ADDED
examples/web_ui/frontend/src/App.tsx ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Onborda, OnbordaProvider } from 'onborda';
2
+ import { useMemo, useState } from 'react';
3
+ import { createBrowserRouter, Navigate, RouterProvider, useNavigate } from 'react-router-dom';
4
+ import { Toaster } from 'sonner';
5
+
6
+ import { RouteError } from '@/components/error/RouteError';
7
+ import { AppLayout } from '@/components/layout/AppLayout';
8
+ import { buildChatTour } from '@/components/tour/chatTourSteps';
9
+ import { TourCard } from '@/components/tour/TourCard';
10
+ import { UploadProvider } from '@/context/UploadContext';
11
+ import { useTranslation } from '@/i18n/useI18n';
12
+ import { ChatPage } from '@/pages/chat';
13
+ import { CredentialPage } from '@/pages/credential';
14
+ import { KnowledgePage } from '@/pages/knowledge';
15
+ import { SchedulePage } from '@/pages/schedule';
16
+ import { SetupPage } from '@/pages/setup';
17
+
18
+ function SetupPageRoute() {
19
+ const navigate = useNavigate();
20
+ return (
21
+ <>
22
+ <div className="h-screen">
23
+ <SetupPage onComplete={() => navigate('/')} />
24
+ </div>
25
+ <Toaster richColors position="top-right" />
26
+ </>
27
+ );
28
+ }
29
+
30
+ const router = createBrowserRouter([
31
+ {
32
+ element: <AppLayout />,
33
+ errorElement: <RouteError />,
34
+ children: [
35
+ {
36
+ // Content-level boundary: a crash in a page replaces only
37
+ // the Outlet area, so AppLayout (the icon rail / nav) stays
38
+ // usable. The parent route keeps its own errorElement as a
39
+ // last-resort catch-all for AppLayout/AppSidebar crashes.
40
+ errorElement: <RouteError />,
41
+ children: [
42
+ { path: '/', element: <Navigate to="/chat" replace /> },
43
+ {
44
+ path: '/chat/:agentId?/:sessionId?/:memberId?',
45
+ element: <ChatPage />,
46
+ },
47
+ { path: '/schedule', element: <SchedulePage /> },
48
+ { path: '/credential', element: <CredentialPage /> },
49
+ { path: '/knowledge', element: <KnowledgePage /> },
50
+ { path: '/knowledge/:kbId', element: <KnowledgePage /> },
51
+ ],
52
+ },
53
+ ],
54
+ },
55
+ { path: '/setup', element: <SetupPageRoute />, errorElement: <RouteError /> },
56
+ ]);
57
+
58
+ function App() {
59
+ const { t } = useTranslation();
60
+ const [setupComplete, setSetupComplete] = useState(() => !!localStorage.getItem('server_url'));
61
+ const tours = useMemo(() => [buildChatTour(t)], [t]);
62
+
63
+ if (!setupComplete) {
64
+ return <SetupPage onComplete={() => setSetupComplete(true)} />;
65
+ }
66
+
67
+ return (
68
+ <OnbordaProvider>
69
+ <Onborda
70
+ steps={tours}
71
+ cardComponent={TourCard}
72
+ shadowOpacity="0.6"
73
+ cardTransition={{ type: 'spring', duration: 0.4 }}
74
+ >
75
+ <UploadProvider>
76
+ <RouterProvider router={router} />
77
+ </UploadProvider>
78
+ <Toaster richColors position="top-right" />
79
+ </Onborda>
80
+ </OnbordaProvider>
81
+ );
82
+ }
83
+
84
+ export default App;
examples/web_ui/frontend/src/api/agent.ts ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { client } from './client';
2
+ import type {
3
+ AgentListResponse,
4
+ AgentRecord,
5
+ AgentSchemaResponse,
6
+ CreateAgentRequest,
7
+ CreateAgentResponse,
8
+ UpdateAgentRequest,
9
+ } from './types';
10
+
11
+ export const agentApi = {
12
+ list: () => client.get<AgentListResponse>('/agent/'),
13
+
14
+ getSchema: () => client.get<AgentSchemaResponse>('/agent/schema'),
15
+
16
+ create: (body: CreateAgentRequest) => client.post<CreateAgentResponse>('/agent/', body),
17
+
18
+ update: (agentId: string, body: UpdateAgentRequest) =>
19
+ client.patch<AgentRecord>(`/agent/${agentId}`, body),
20
+
21
+ delete: (agentId: string) => client.delete(`/agent/${agentId}`),
22
+ };
examples/web_ui/frontend/src/api/chat.ts ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { client } from './client';
2
+ import type { ChatRequest } from './types';
3
+
4
+ /**
5
+ * Chat API — fire-and-forget trigger for chat runs.
6
+ *
7
+ * Events produced by the run are delivered via the session's SSE
8
+ * stream endpoint (``GET /sessions/{sid}/stream``), not in the
9
+ * response body of this POST.
10
+ */
11
+ export const chatApi = {
12
+ /**
13
+ * Trigger a chat run for the specified session.
14
+ *
15
+ * Accepts user messages, human-in-the-loop confirmation events,
16
+ * or ``null`` (continue from current state). Returns immediately;
17
+ * the caller should already be subscribed to the session's SSE
18
+ * stream to receive the resulting events.
19
+ *
20
+ * @param body - The chat request payload.
21
+ * @returns A confirmation object ``{ status, session_id }``.
22
+ */
23
+ trigger: (body: ChatRequest) =>
24
+ client.post<{ status: string; session_id: string }>('/chat/', body),
25
+ };
examples/web_ui/frontend/src/api/client.ts ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { toast } from 'sonner';
2
+
3
+ export const getBaseUrl = () => localStorage.getItem('server_url') ?? '';
4
+ export const getUserId = () => localStorage.getItem('username') ?? '';
5
+
6
+ /**
7
+ * Structured error thrown for non-2xx HTTP responses.
8
+ * `message` contains the human-readable detail extracted from the backend.
9
+ */
10
+ export class ApiError extends Error {
11
+ readonly status: number;
12
+ readonly detail: string;
13
+
14
+ constructor(status: number, detail: string) {
15
+ super(detail);
16
+ this.name = 'ApiError';
17
+ this.status = status;
18
+ this.detail = detail;
19
+ }
20
+ }
21
+
22
+ interface RequestOptions {
23
+ method?: string;
24
+ body?: unknown;
25
+ params?: Record<string, string>;
26
+ /** When true, suppresses the automatic error toast. Useful when the caller shows its own inline error UI. */
27
+ silent?: boolean;
28
+ }
29
+
30
+ function buildHeaders(hasBody: boolean): Record<string, string> {
31
+ const headers: Record<string, string> = { 'X-User-ID': getUserId() };
32
+ if (hasBody) headers['Content-Type'] = 'application/json';
33
+ return headers;
34
+ }
35
+
36
+ /** Parse the response body and extract the `detail` field if the backend returned JSON. */
37
+ async function extractErrorDetail(res: Response): Promise<string> {
38
+ const text = await res.text();
39
+ try {
40
+ const json = JSON.parse(text) as { detail?: unknown };
41
+ if (typeof json.detail === 'string') return json.detail;
42
+ if (json.detail !== undefined) return JSON.stringify(json.detail);
43
+ } catch {
44
+ // not JSON – fall through
45
+ }
46
+ return text || res.statusText;
47
+ }
48
+
49
+ async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
50
+ const { method = 'GET', body, params, silent = false } = options;
51
+ const url = new URL(path, getBaseUrl());
52
+ if (params) {
53
+ Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
54
+ }
55
+
56
+ const res = await fetch(url.toString(), {
57
+ method,
58
+ headers: buildHeaders(body !== undefined),
59
+ body: body ? JSON.stringify(body) : undefined,
60
+ });
61
+
62
+ if (!res.ok) {
63
+ const detail = await extractErrorDetail(res);
64
+ const error = new ApiError(res.status, detail);
65
+ if (!silent) toast.error(detail);
66
+ throw error;
67
+ }
68
+
69
+ if (res.status === 204) return undefined as T;
70
+ return res.json() as Promise<T>;
71
+ }
72
+
73
+ async function streamRequest(
74
+ path: string,
75
+ options: RequestOptions & { signal?: AbortSignal } = {},
76
+ ): Promise<Response> {
77
+ const { method = 'GET', body, params, signal, silent = false } = options;
78
+ const url = new URL(path, getBaseUrl());
79
+ if (params) {
80
+ Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
81
+ }
82
+
83
+ const res = await fetch(url.toString(), {
84
+ method,
85
+ headers: buildHeaders(body !== undefined),
86
+ body: body ? JSON.stringify(body) : undefined,
87
+ signal,
88
+ });
89
+
90
+ if (!res.ok) {
91
+ const detail = await extractErrorDetail(res);
92
+ const error = new ApiError(res.status, detail);
93
+ if (!silent) toast.error(detail);
94
+ throw error;
95
+ }
96
+
97
+ return res;
98
+ }
99
+
100
+ export const client = {
101
+ get: <T>(path: string, params?: Record<string, string>) =>
102
+ request<T>(path, { method: 'GET', params }),
103
+ post: <T>(path: string, body?: unknown, params?: Record<string, string>) =>
104
+ request<T>(path, { method: 'POST', body, params }),
105
+ patch: <T>(path: string, body?: unknown, params?: Record<string, string>) =>
106
+ request<T>(path, { method: 'PATCH', body, params }),
107
+ delete: <T = void>(path: string, params?: Record<string, string>) =>
108
+ request<T>(path, { method: 'DELETE', params }),
109
+ stream: (path: string, options?: RequestOptions & { signal?: AbortSignal }) =>
110
+ streamRequest(path, options),
111
+ };
examples/web_ui/frontend/src/api/credential.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { client } from './client';
2
+ import type {
3
+ CreateCredentialRequest,
4
+ CreateCredentialResponse,
5
+ CredentialListResponse,
6
+ CredentialRecord,
7
+ CredentialSchemasResponse,
8
+ UpdateCredentialRequest,
9
+ } from './types';
10
+
11
+ export const credentialApi = {
12
+ list: () => client.get<CredentialListResponse>('/credential/'),
13
+
14
+ schemas: () => client.get<CredentialSchemasResponse>('/credential/schemas'),
15
+
16
+ create: (body: CreateCredentialRequest) =>
17
+ client.post<CreateCredentialResponse>('/credential/', body),
18
+
19
+ update: (credentialId: string, body: UpdateCredentialRequest) =>
20
+ client.patch<CredentialRecord>(`/credential/${credentialId}`, body),
21
+
22
+ delete: (credentialId: string) => client.delete(`/credential/${credentialId}`),
23
+ };
examples/web_ui/frontend/src/api/index.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export * from './types';
2
+ export { agentApi } from './agent';
3
+ export { sessionApi } from './session';
4
+ export { credentialApi } from './credential';
5
+ export { chatApi } from './chat';
6
+ export { workspaceApi } from './workspace';
7
+ export { scheduleApi } from './schedule';
8
+ export { modelApi, ttsModelApi } from './model';
9
+ export { knowledgeBaseApi } from './knowledgeBase';
examples/web_ui/frontend/src/api/knowledgeBase.ts ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ApiError, client, getBaseUrl, getUserId } from './client';
2
+ import type {
3
+ CreateKnowledgeBaseRequest,
4
+ CreateKnowledgeBaseResponse,
5
+ KbMiddlewareParametersSchemaResponse,
6
+ KnowledgeBaseView,
7
+ ListKbEmbeddingModelsResponse,
8
+ ListKnowledgeBasesResponse,
9
+ ListKnowledgeDocumentsResponse,
10
+ ListKnowledgeDocumentStatusResponse,
11
+ ListSupportedContentTypesResponse,
12
+ SearchKnowledgeBaseRequest,
13
+ SearchKnowledgeBaseResponse,
14
+ UpdateKnowledgeBaseRequest,
15
+ UploadKnowledgeDocumentResponse,
16
+ } from './types';
17
+
18
+ /**
19
+ * Callback invoked while bytes are pushed across the wire.
20
+ *
21
+ * - `loaded` — bytes already sent.
22
+ * - `total` — total bytes (may be 0 when the browser cannot compute it,
23
+ * e.g. for chunked encodings).
24
+ */
25
+ export interface UploadProgress {
26
+ loaded: number;
27
+ total: number;
28
+ }
29
+
30
+ export interface UploadDocumentOptions {
31
+ /** Fired with byte-level progress while the body is streamed. */
32
+ onProgress?: (progress: UploadProgress) => void;
33
+ /**
34
+ * Caller-supplied abort signal. Aborting before the server has
35
+ * responded rejects the returned promise with a `DOMException` of
36
+ * `name === "AbortError"`; aborting after a response has come back
37
+ * is a no-op.
38
+ */
39
+ signal?: AbortSignal;
40
+ }
41
+
42
+ /**
43
+ * XHR-based upload — `fetch` does not surface byte-level send
44
+ * progress in any current browser, so multipart uploads that drive a
45
+ * progress UI have to fall back to XMLHttpRequest.
46
+ */
47
+ function uploadDocumentXhr(
48
+ knowledgeBaseId: string,
49
+ file: File,
50
+ options: UploadDocumentOptions = {},
51
+ ): Promise<UploadKnowledgeDocumentResponse> {
52
+ const { onProgress, signal } = options;
53
+ const formData = new FormData();
54
+ formData.append('file', file);
55
+
56
+ return new Promise((resolve, reject) => {
57
+ if (signal?.aborted) {
58
+ reject(new DOMException('Aborted', 'AbortError'));
59
+ return;
60
+ }
61
+
62
+ const xhr = new XMLHttpRequest();
63
+ const url = new URL(`/knowledge_bases/${knowledgeBaseId}/documents`, getBaseUrl());
64
+ xhr.open('POST', url.toString(), true);
65
+ xhr.setRequestHeader('X-User-ID', getUserId());
66
+
67
+ const onAbort = () => xhr.abort();
68
+ signal?.addEventListener('abort', onAbort, { once: true });
69
+
70
+ const cleanup = () => signal?.removeEventListener('abort', onAbort);
71
+
72
+ if (xhr.upload && onProgress) {
73
+ xhr.upload.onprogress = (e) => {
74
+ onProgress({
75
+ loaded: e.loaded,
76
+ total: e.lengthComputable ? e.total : 0,
77
+ });
78
+ };
79
+ }
80
+
81
+ xhr.onload = () => {
82
+ cleanup();
83
+ if (xhr.status >= 200 && xhr.status < 300) {
84
+ try {
85
+ resolve(JSON.parse(xhr.responseText) as UploadKnowledgeDocumentResponse);
86
+ } catch (e) {
87
+ reject(e);
88
+ }
89
+ return;
90
+ }
91
+ let detail = xhr.responseText || xhr.statusText;
92
+ try {
93
+ const json = JSON.parse(xhr.responseText) as {
94
+ detail?: unknown;
95
+ };
96
+ if (typeof json.detail === 'string') detail = json.detail;
97
+ else if (json.detail !== undefined) detail = JSON.stringify(json.detail);
98
+ } catch {
99
+ // keep raw text
100
+ }
101
+ reject(new ApiError(xhr.status, detail));
102
+ };
103
+ xhr.onerror = () => {
104
+ cleanup();
105
+ reject(new ApiError(0, 'Network error'));
106
+ };
107
+ xhr.onabort = () => {
108
+ cleanup();
109
+ reject(new DOMException('Aborted', 'AbortError'));
110
+ };
111
+
112
+ xhr.send(formData);
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Client for the `/knowledge_bases` router.
118
+ */
119
+ export const knowledgeBaseApi = {
120
+ list: () => client.get<ListKnowledgeBasesResponse>('/knowledge_bases/'),
121
+
122
+ listEmbeddingModels: () =>
123
+ client.get<ListKbEmbeddingModelsResponse>('/knowledge_bases/embedding_models'),
124
+
125
+ /** Fetch the JSON Schema describing the KB middleware's tunable params. */
126
+ middlewareParametersSchema: () =>
127
+ client.get<KbMiddlewareParametersSchemaResponse>(
128
+ '/knowledge_bases/middleware/parameters_schema',
129
+ ),
130
+
131
+ /** List the union of media types + extensions every parser accepts. */
132
+ supportedContentTypes: () =>
133
+ client.get<ListSupportedContentTypesResponse>('/knowledge_bases/supported_content_types'),
134
+
135
+ create: (body: CreateKnowledgeBaseRequest) =>
136
+ client.post<CreateKnowledgeBaseResponse>('/knowledge_bases/', body),
137
+
138
+ update: (knowledgeBaseId: string, body: UpdateKnowledgeBaseRequest) =>
139
+ client.patch<KnowledgeBaseView>(`/knowledge_bases/${knowledgeBaseId}`, body),
140
+
141
+ delete: (knowledgeBaseId: string) => client.delete(`/knowledge_bases/${knowledgeBaseId}`),
142
+
143
+ /** List every document registered against a knowledge base. */
144
+ listDocuments: (knowledgeBaseId: string) =>
145
+ client.get<ListKnowledgeDocumentsResponse>(`/knowledge_bases/${knowledgeBaseId}/documents`),
146
+
147
+ /**
148
+ * Batch-query lifecycle status for a list of documents.
149
+ *
150
+ * Missing ids are silently omitted by the server, so the response
151
+ * may be shorter than the input. An empty `ids` short-circuits
152
+ * locally — the backend treats an empty list as a 200 with
153
+ * `items: []`, but skipping the round-trip is friendlier to the
154
+ * polling loop.
155
+ */
156
+ getDocumentStatus: (knowledgeBaseId: string, ids: string[]) => {
157
+ if (ids.length === 0) {
158
+ return Promise.resolve<ListKnowledgeDocumentStatusResponse>({
159
+ items: [],
160
+ });
161
+ }
162
+ return client.get<ListKnowledgeDocumentStatusResponse>(
163
+ `/knowledge_bases/${knowledgeBaseId}/documents/status`,
164
+ { ids: ids.join(',') },
165
+ );
166
+ },
167
+
168
+ uploadDocument: (knowledgeBaseId: string, file: File, options?: UploadDocumentOptions) =>
169
+ uploadDocumentXhr(knowledgeBaseId, file, options),
170
+
171
+ deleteDocument: (knowledgeBaseId: string, documentId: string) =>
172
+ client.delete(`/knowledge_bases/${knowledgeBaseId}/documents/${documentId}`),
173
+
174
+ search: (knowledgeBaseId: string, body: SearchKnowledgeBaseRequest) =>
175
+ client.post<SearchKnowledgeBaseResponse>(
176
+ `/knowledge_bases/${knowledgeBaseId}/search`,
177
+ body,
178
+ ),
179
+ };
examples/web_ui/frontend/src/api/model.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { client } from './client';
2
+ import type { ListModelResponse, ListTTSModelResponse } from './types';
3
+
4
+ export const modelApi = {
5
+ list: (provider: string) => client.get<ListModelResponse>('/model/', { provider }),
6
+ };
7
+
8
+ export const ttsModelApi = {
9
+ list: (provider: string) => client.get<ListTTSModelResponse>('/tts-model/', { provider }),
10
+ };