This is an automated email from the ASF dual-hosted git repository.
robocanic pushed a commit to branch ai
in repository https://gitbox.apache.org/repos/asf/dubbo-admin.git
The following commit(s) were added to refs/heads/ai by this push:
new 60db2ded [feat]: Integrating AI Agent Capabilities into dubbo-admin
60db2ded is described below
commit 60db2dedacd2c873fb5d122789901fb53baf831f
Author: Liwen Chen <[email protected]>
AuthorDate: Wed Sep 3 19:34:03 2025 +0800
[feat]: Integrating AI Agent Capabilities into dubbo-admin
Integrating AI Agent Capabilities into dubbo-admin(#1319)
---
ai/.env.example | 3 +
ai/.gitignore | 6 +
ai/README.md | 0
ai/go.mod | 60 ++++
ai/go.sum | 123 +++++++
ai/internal/agent/flow.go | 182 ++++++++++
ai/internal/config/config.go | 25 ++
ai/internal/manager/manager.go | 126 +++++++
ai/internal/schema/llm_schema.go | 102 ++++++
ai/internal/tools/mock_tools.go | 623 ++++++++++++++++++++++++++++++++++
ai/internal/tools/tools.go | 39 +++
ai/main.go | 13 +
ai/plugins/dashscope/dashscope.go | 148 ++++++++
ai/plugins/siliconflow/siliconflow.go | 141 ++++++++
ai/prompts/agentSystem.prompt | 90 +++++
ai/prompts/menuPrompt.prompt | 15 +
ai/prompts/weatherPrompt.prompt | 12 +
ai/test/flow_test.go | 107 ++++++
ai/test/llm_test.go | 66 ++++
ai/test/test.go | 12 +
ai/utils/utils.go | 52 +++
21 files changed, 1945 insertions(+)
diff --git a/ai/.env.example b/ai/.env.example
new file mode 100644
index 00000000..7f47aff9
--- /dev/null
+++ b/ai/.env.example
@@ -0,0 +1,3 @@
+GEMINI_API_KEY=YOUR_API_KEY
+SILICONFLOW_API_KEY=YOUR_API_KEY
+DASHSCOPE_API_KEY=YOUR_API_KEY
\ No newline at end of file
diff --git a/ai/.gitignore b/ai/.gitignore
new file mode 100644
index 00000000..a96daa9e
--- /dev/null
+++ b/ai/.gitignore
@@ -0,0 +1,6 @@
+.idea
+.vscode
+.genkit
+.DS_Store
+
+.env
\ No newline at end of file
diff --git a/ai/README.md b/ai/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/ai/go.mod b/ai/go.mod
new file mode 100644
index 00000000..f0fa8b8d
--- /dev/null
+++ b/ai/go.mod
@@ -0,0 +1,60 @@
+module dubbo-admin-ai
+
+go 1.24.1
+
+toolchain go1.24.5
+
+require (
+ github.com/firebase/genkit/go v0.6.2
+ github.com/joho/godotenv v1.5.1
+ github.com/lmittmann/tint v1.1.2
+ github.com/openai/openai-go v0.1.0-alpha.65
+)
+
+require (
+ github.com/tidwall/gjson v1.18.0 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.1 // indirect
+ github.com/tidwall/sjson v1.2.5 // indirect
+)
+
+require (
+ cloud.google.com/go v0.120.0 // indirect
+ cloud.google.com/go/auth v0.16.2 // indirect
+ cloud.google.com/go/compute/metadata v0.7.0 // indirect
+ github.com/bahlo/generic-list-go v0.2.0 // indirect
+ github.com/buger/jsonparser v1.1.1 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/go-logr/logr v1.4.3 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/goccy/go-yaml v1.17.1 // indirect
+ github.com/google/dotprompt/go v0.0.0-20250611200215-bb73406b05ca //
indirect
+ github.com/google/go-cmp v0.7.0 // indirect
+ github.com/google/s2a-go v0.1.9 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
+ github.com/googleapis/gax-go/v2 v2.14.2 // indirect
+ github.com/gorilla/websocket v1.5.3 // indirect
+ github.com/invopop/jsonschema v0.13.0 // indirect
+ github.com/mailru/easyjson v0.9.0 // indirect
+ github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a //
indirect
+ github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
+ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb //
indirect
+ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415
// indirect
+ github.com/xeipuuv/gojsonschema v1.2.0 // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0
// indirect
+ go.opentelemetry.io/otel v1.36.0 // indirect
+ go.opentelemetry.io/otel/metric v1.36.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.36.0 // indirect
+ go.opentelemetry.io/otel/trace v1.36.0 // indirect
+ golang.org/x/crypto v0.41.0 // indirect
+ golang.org/x/net v0.43.0 // indirect
+ golang.org/x/sys v0.35.0 // indirect
+ golang.org/x/text v0.28.0 // indirect
+ google.golang.org/genai v1.11.1 // indirect
+ google.golang.org/genproto/googleapis/rpc
v0.0.0-20250603155806-513f23925822 // indirect
+ google.golang.org/grpc v1.73.0 // indirect
+ google.golang.org/protobuf v1.36.6 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/ai/go.sum b/ai/go.sum
new file mode 100644
index 00000000..ab1e3298
--- /dev/null
+++ b/ai/go.sum
@@ -0,0 +1,123 @@
+cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
+cloud.google.com/go v0.120.0/go.mod
h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
+cloud.google.com/go/auth v0.16.2
h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
+cloud.google.com/go/auth v0.16.2/go.mod
h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
+cloud.google.com/go/compute/metadata v0.7.0
h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
+cloud.google.com/go/compute/metadata v0.7.0/go.mod
h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
+github.com/bahlo/generic-list-go v0.2.0
h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
+github.com/bahlo/generic-list-go v0.2.0/go.mod
h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
+github.com/buger/jsonparser v1.1.1
h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
+github.com/buger/jsonparser v1.1.1/go.mod
h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
+github.com/davecgh/go-spew v1.1.0/go.mod
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/felixge/httpsnoop v1.0.4
h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod
h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/firebase/genkit/go v0.6.2
h1:FaVJtcprfXZz0gXTtARJqUiovu/R2wuJycNn/18aNMc=
+github.com/firebase/genkit/go v0.6.2/go.mod
h1:blRYK6oNgwBDX6F+gInACru6q527itviv+xruiMSUuU=
+github.com/go-logr/logr v1.2.2/go.mod
h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod
h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod
h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/goccy/go-yaml v1.17.1
h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
+github.com/goccy/go-yaml v1.17.1/go.mod
h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
+github.com/golang/protobuf v1.5.4
h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod
h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/dotprompt/go v0.0.0-20250611200215-bb73406b05ca
h1:LuQ8KS5N04c37jyaq6jelLdNi0GfI6QJb8lpnYaDW9Y=
+github.com/google/dotprompt/go v0.0.0-20250611200215-bb73406b05ca/go.mod
h1:dnIk+MSMnipm9uZyPIgptq7I39aDxyjBiaev/OG0W0Y=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod
h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
+github.com/google/s2a-go v0.1.9/go.mod
h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.3.6
h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
+github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod
h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
+github.com/googleapis/gax-go/v2 v2.14.2
h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
+github.com/googleapis/gax-go/v2 v2.14.2/go.mod
h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
+github.com/gorilla/websocket v1.5.3
h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod
h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/invopop/jsonschema v0.13.0
h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
+github.com/invopop/jsonschema v0.13.0/go.mod
h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod
h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod
h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lmittmann/tint v1.1.2
h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
+github.com/lmittmann/tint v1.1.2/go.mod
h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
+github.com/mailru/easyjson v0.9.0
h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
+github.com/mailru/easyjson v0.9.0/go.mod
h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
+github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a
h1:v2cBA3xWKv2cIOVhnzX/gNgkNXqiHfUgJtA3r61Hf7A=
+github.com/mbleigh/raymond v0.0.0-20250414171441-6b3a58ab9e0a/go.mod
h1:Y6ghKH+ZijXn5d9E7qGGZBmjitx7iitZdQiIW97EpTU=
+github.com/openai/openai-go v0.1.0-alpha.65
h1:G12sA6OaL+cVMElMO3m5RVFwKhhg40kmGeGhaYZIoYw=
+github.com/openai/openai-go v0.1.0-alpha.65/go.mod
h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A=
+github.com/pmezard/go-difflib v1.0.0/go.mod
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.13.1
h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod
h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/stretchr/objx v0.1.0/go.mod
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod
h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.10.0
h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tidwall/gjson v1.14.2/go.mod
h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.18.0
h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
+github.com/tidwall/gjson v1.18.0/go.mod
h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod
h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0/go.mod
h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/pretty v1.2.1
h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
+github.com/tidwall/pretty v1.2.1/go.mod
h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
+github.com/tidwall/sjson v1.2.5/go.mod
h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
+github.com/wk8/go-ordered-map/v2 v2.1.8
h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
+github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod
h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod
h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb
h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod
h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415
h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod
h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0
h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod
h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+go.opentelemetry.io/auto/sdk v1.1.0
h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod
h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0
h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod
h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
+go.opentelemetry.io/otel v1.36.0
h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
+go.opentelemetry.io/otel v1.36.0/go.mod
h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
+go.opentelemetry.io/otel/metric v1.36.0
h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
+go.opentelemetry.io/otel/metric v1.36.0/go.mod
h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
+go.opentelemetry.io/otel/sdk v1.36.0
h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
+go.opentelemetry.io/otel/sdk v1.36.0/go.mod
h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
+go.opentelemetry.io/otel/sdk/metric v1.36.0
h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
+go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod
h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
+go.opentelemetry.io/otel/trace v1.36.0
h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
+go.opentelemetry.io/otel/trace v1.36.0/go.mod
h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod
h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
+golang.org/x/crypto v0.41.0/go.mod
h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
+golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
+golang.org/x/sync v0.16.0/go.mod
h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod
h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
+google.golang.org/genai v1.11.1 h1:MgI2JVDaIQ1YMuzKFwgPciB+K6kQ8MCBMVL9u7Oa8qw=
+google.golang.org/genai v1.11.1/go.mod
h1:HFXR1zT3LCdLxd/NW6IOSCczOYyRAxwaShvYbgPSeVw=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822
h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
+google.golang.org/genproto/googleapis/rpc
v0.0.0-20250603155806-513f23925822/go.mod
h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
+google.golang.org/grpc v1.73.0/go.mod
h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
+google.golang.org/protobuf v1.36.6
h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod
h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod
h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/ai/internal/agent/flow.go b/ai/internal/agent/flow.go
new file mode 100644
index 00000000..e39e359e
--- /dev/null
+++ b/ai/internal/agent/flow.go
@@ -0,0 +1,182 @@
+package agent
+
+import (
+ "context"
+
+ "dubbo-admin-ai/internal/config"
+ "dubbo-admin-ai/internal/manager"
+ "dubbo-admin-ai/internal/schema"
+ "dubbo-admin-ai/internal/tools"
+ "errors"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/firebase/genkit/go/core/logger"
+
+ "github.com/firebase/genkit/go/ai"
+ "github.com/firebase/genkit/go/core"
+ "github.com/firebase/genkit/go/genkit"
+)
+
+// 公开的 Flow 变量,以便在编排器中调用
+var (
+ ReActFlow *core.Flow[schema.ReActIn, schema.ReActOut, struct{}]
+ ThinkingFlow *core.Flow[schema.ThinkIn, *schema.ThinkOut, struct{}]
+ ActFlow *core.Flow[*schema.ActIn, schema.ActOut, struct{}]
+
+ ThinkPrompt *ai.Prompt
+)
+
+var g *genkit.Genkit
+
+// The order of initialization cannot change
+func InitAgent(defaultModel string) (err error) {
+ if err = manager.LoadEnvVars(); err != nil {
+ return err
+ }
+
+ manager.InitLogger()
+ if err := manager.InitGlobalGenkit(defaultModel); err != nil {
+ return err
+ }
+
+ if g, err = manager.GetGlobalGenkit(); err != nil {
+ return err
+ }
+
+ tools.RegisterAllMockTools(g)
+
+ if err = InitFlows(g); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func InitFlows(registry *genkit.Genkit) error {
+ if registry == nil {
+ return fmt.Errorf("registry is nil")
+ }
+ g = registry
+
+ data, err := os.ReadFile(config.PROMPT_DIR_PATH + "/agentSystem.prompt")
+ if err != nil {
+ return fmt.Errorf("failed to read agentSystem prompt: %w", err)
+ }
+ systemPromptText := string(data)
+
+ mockTools, err := tools.AllMockToolRef()
+ if err != nil {
+ log.Fatalf("failed to get mock mock_tools: %v", err)
+ }
+ ThinkPrompt, err = genkit.DefinePrompt(g, "agentThinking",
+ ai.WithSystem(systemPromptText),
+ ai.WithInputType(schema.ThinkIn{}),
+ ai.WithOutputType(schema.ThinkOut{}),
+ ai.WithPrompt("{{userInput}}"),
+ ai.WithTools(mockTools...),
+ )
+
+ if err != nil {
+ return fmt.Errorf("failed to define agentThink prompt: %w", err)
+ }
+
+ ReActFlow = genkit.DefineFlow(g, "reAct", reAct)
+ ThinkingFlow = genkit.DefineFlow(g, "thinking", thinking)
+ ActFlow = genkit.DefineFlow(g, "act", act)
+
+ return nil
+}
+
+// Flow 的核心函数实现 `fn` (不对外导出)
+// ----------------------------------------------------------------------------
+// 1. agentOrchestrator: 总指挥/编排器的核心逻辑
+// ----------------------------------------------------------------------------
+func reAct(ctx context.Context, reActInput schema.ReActIn) (reActOut
schema.ReActOut, err error) {
+ //TODO: 输入数据意图解析
+
+ thinkingInput := reActInput
+ // 主协调循环 (Reconciliation Loop)
+ for i := 0; i < config.MAX_REACT_ITERATIONS; i++ {
+
+ // a. 调用 thinkingFlow
+ thinkingResp, err := ThinkingFlow.Run(ctx, thinkingInput)
+ if err != nil {
+ manager.GetLogger().Error("failed to run thinking
flow", "error", err)
+ return "", err
+ }
+ if thinkingResp == nil {
+ manager.GetLogger().Error("expected non-nil response")
+ return "", errors.New("expected non-nil response")
+ }
+
+ for _, r := range thinkingResp.ToolRequests {
+ manager.GetLogger().Info(r.String())
+ }
+
+ reActOut = thinkingResp.String()
+
+ // b. 检查是否有最终答案
+ if thinkingResp.IsStop() {
+ break
+ }
+
+ // c. 调用 actFlow
+ actOutArray, err := ActFlow.Run(ctx, thinkingResp)
+ if err != nil {
+ logger.FromContext(ctx).Error("failed to run act flow",
"error", err)
+ return "", err
+ }
+
+ // TODO: 这里将来会集成SessionMemory,暂时简化处理
+ _ = actOutArray // 占位符,避免未使用变量错误
+ }
+
+ return reActOut, nil
+}
+
+// ----------------------------------------------------------------------------
+// 2. thinking: 大脑/思考者的核心逻辑
+// ----------------------------------------------------------------------------
+func thinking(ctx context.Context, input schema.ThinkIn) (*schema.ThinkOut,
error) {
+ resp, err := ThinkPrompt.Execute(ctx,
+ ai.WithInput(input),
+ )
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to execute agentThink prompt:
%w", err)
+ }
+
+ // 解析输出
+ var response *schema.ThinkOut
+ err = resp.Output(&response)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse agentThink prompt
response: %w", err)
+ }
+
+ return response, nil
+}
+
+// ----------------------------------------------------------------------------
+// 3. act: 执行者的核心逻辑
+// TODO: Genkit 的 Tool 设计不能保证任意 LLM 一定能执行工具调用,是否考虑设计成所有模型都能执行?
+// 一种思路是让 LLM 直接生成工具调用的结构化描述,然后由适配器执行。
+// 管线、流水线的思想能否用在这里?
+func act(ctx context.Context, actIn *schema.ActIn) (schema.ActOut, error) {
+ var actOutArray schema.ActOut
+ // 执行工具调用
+ for _, resp := range actIn.ToolRequests {
+ if err := resp.ValidateToolDesc(); err != nil {
+ return nil, fmt.Errorf("invalid tool description in
action response: %w", err)
+ }
+
+ output, err := resp.ToolDesc.Call(g, ctx)
+ if err != nil {
+ return nil, fmt.Errorf("failed to call tool %s: %w",
resp.ToolDesc.ToolName, err)
+ }
+ actOutArray = append(actOutArray, output)
+ }
+
+ return actOutArray, nil
+}
diff --git a/ai/internal/config/config.go b/ai/internal/config/config.go
new file mode 100644
index 00000000..b071b846
--- /dev/null
+++ b/ai/internal/config/config.go
@@ -0,0 +1,25 @@
+package config
+
+import (
+ "dubbo-admin-ai/plugins/siliconflow"
+ "os"
+ "path/filepath"
+ "runtime"
+)
+
+var (
+ // API keys
+ GEMINI_API_KEY string = os.Getenv("GEMINI_API_KEY")
+ SILICONFLOW_API_KEY string = os.Getenv("SILICONFLOW_API_KEY")
+ DASHSCOPE_API_KEY string = os.Getenv("DASHSCOPE_API_KEY")
+
+ // Configuration
+ // 自动获取项目根目录
+ _, b, _, _ = runtime.Caller(0)
+ PROJECT_ROOT = filepath.Join(filepath.Dir(b), "..", "..")
+
+ PROMPT_DIR_PATH string = filepath.Join(PROJECT_ROOT, "prompts")
+ LOG_LEVEL string = os.Getenv("LOG_LEVEL")
+ DEFAULT_MODEL string = siliconflow.DeepSeekV3
+ MAX_REACT_ITERATIONS int = 5
+)
diff --git a/ai/internal/manager/manager.go b/ai/internal/manager/manager.go
new file mode 100644
index 00000000..2c0e8d17
--- /dev/null
+++ b/ai/internal/manager/manager.go
@@ -0,0 +1,126 @@
+package manager
+
+import (
+ "context"
+
+ "dubbo-admin-ai/internal/config"
+
+ "dubbo-admin-ai/plugins/dashscope"
+ "dubbo-admin-ai/plugins/siliconflow"
+ "dubbo-admin-ai/utils"
+ "fmt"
+ "log/slog"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/firebase/genkit/go/core/logger"
+ "github.com/firebase/genkit/go/genkit"
+ "github.com/firebase/genkit/go/plugins/googlegenai"
+ "github.com/joho/godotenv"
+ "github.com/lmittmann/tint"
+)
+
+var (
+ globalGenkit *genkit.Genkit
+ rootContext *context.Context
+ globalLogger *slog.Logger
+)
+
+func InitGlobalGenkit(defaultModel string) (err error) {
+ ctx := context.Background()
+ if rootContext == nil {
+ rootContext = &ctx
+ }
+ g, err := genkit.Init(*rootContext,
+ genkit.WithPlugins(
+ &siliconflow.SiliconFlow{
+ APIKey: config.SILICONFLOW_API_KEY,
+ },
+ &googlegenai.GoogleAI{
+ APIKey: config.GEMINI_API_KEY,
+ },
+ &dashscope.DashScope{
+ APIKey: config.DASHSCOPE_API_KEY,
+ },
+ ),
+ genkit.WithDefaultModel(defaultModel),
+ genkit.WithPromptDir(config.PROMPT_DIR_PATH),
+ )
+
+ if g == nil {
+ return fmt.Errorf("fail to initialize global genkit")
+ }
+
+ globalGenkit = g
+ return err
+}
+
+func InitLogger() {
+ logLevel := slog.LevelInfo
+ if envLevel := config.LOG_LEVEL; envLevel != "" {
+ switch strings.ToUpper(envLevel) {
+ case "DEBUG":
+ logLevel = slog.LevelDebug
+ case "INFO":
+ logLevel = slog.LevelInfo
+ case "WARN", "WARNING":
+ logLevel = slog.LevelWarn
+ case "ERROR":
+ logLevel = slog.LevelError
+ }
+ }
+ logger.SetLevel(logLevel)
+
+ slog.SetDefault(
+ slog.New(
+ tint.NewHandler(os.Stderr, &tint.Options{
+ Level: slog.LevelDebug,
+ AddSource: true,
+ TimeFormat: time.Kitchen,
+ }),
+ ),
+ )
+ globalLogger = slog.Default()
+}
+
+func GetGlobalGenkit() (*genkit.Genkit, error) {
+ var err error
+ if globalGenkit == nil {
+ return nil, fmt.Errorf("global genkit is nil, initialize genkit
first")
+ }
+ return globalGenkit, err
+}
+
+func GetLogger() *slog.Logger {
+ if globalLogger == nil {
+ InitLogger()
+ }
+ return globalLogger
+}
+
+func GetRootContext() context.Context {
+ ctx := context.Background()
+ if rootContext == nil {
+ rootContext = &ctx
+ }
+ return *rootContext
+}
+
+// Load environment variables from PROJECT_ROOT/.env file
+func LoadEnvVars() (err error) {
+ dotEnvFilePath := filepath.Join(config.PROJECT_ROOT, ".env")
+ dotEnvExampleFilePath := filepath.Join(config.PROJECT_ROOT,
".env.example")
+
+ // Check if the .env file exists, if not, copy .env.example to .env
+ if _, err = os.Stat(dotEnvFilePath); os.IsNotExist(err) {
+ if err = utils.CopyFile(dotEnvExampleFilePath, dotEnvFilePath);
err != nil {
+ return err
+ }
+ }
+
+ // Load environment variables
+ err = godotenv.Load(dotEnvFilePath)
+ return err
+}
diff --git a/ai/internal/schema/llm_schema.go b/ai/internal/schema/llm_schema.go
new file mode 100644
index 00000000..f54f1f7d
--- /dev/null
+++ b/ai/internal/schema/llm_schema.go
@@ -0,0 +1,102 @@
+package schema
+
+import (
+ "dubbo-admin-ai/internal/tools"
+ "encoding/json"
+ "fmt"
+)
+
+// 表示 LLM 的响应结构
+type ReActIn = ReActInput
+type ReActOut = string
+
+type ThinkIn = ReActInput
+type ThinkOut = ThinkAggregation
+
+type ActIn = ThinkOut
+type ActOut = []string
+
+type ReActInput struct {
+ UserInput string `json:"userInput"`
+ // 移除SessionID和SessionMemory,这些由Session管理
+}
+
+type Status string
+
+const (
+ Continued Status = "CONTINUED"
+ Finished Status = "FINISHED"
+ Pending Status = "PENDING"
+)
+
+type ToolRequest struct {
+ Thought string `json:"thought"`
+ ToolDesc tools.ToolDesc `json:"tool_desc"`
+}
+
+type ThinkAggregation struct {
+ ToolRequests []ToolRequest `json:"tool_requests"`
+ Status Status `json:"status"
jsonschema:"enum=CONTINUED,enum=FINISHED,enum=PENDING"`
+ Thought string `json:"thought"`
+ FinalAnswer string `json:"final_answer,omitempty"
jsonschema:"required=false"`
+}
+
+// ReActInput
+func (a ReActInput) String() string {
+ data, err := json.MarshalIndent(a, "", " ")
+ if err != nil {
+ return fmt.Sprintf("ReActInput{error: %v}", err)
+ }
+ return string(data)
+}
+
+// ToolRequest
+// HasAction 检查是否包含行动指令
+func (tr ToolRequest) HasAction() bool {
+ return tr.ToolDesc.ToolInput != nil && tr.ToolDesc.ToolName != ""
+}
+
+// ValidateToolDesc 验证行动指令的有效性
+func (tr ToolRequest) ValidateToolDesc() error {
+ if !tr.HasAction() {
+ return fmt.Errorf("no valid action found")
+ }
+
+ if tr.ToolDesc.ToolName == "" {
+ return fmt.Errorf("tool_name cannot be empty")
+ }
+
+ // 验证支持的工具名称
+ supportedTools, err := tools.AllMockToolNames()
+ if err != nil {
+ return fmt.Errorf("failed to get supported tools: %w", err)
+ }
+
+ if _, ok := supportedTools[tr.ToolDesc.ToolName]; !ok {
+ return fmt.Errorf("unsupported tool: %s", tr.ToolDesc.ToolName)
+ }
+
+ return nil
+}
+
+// String 实现 fmt.Stringer 接口,返回格式化的 JSON
+func (tr ToolRequest) String() string {
+ data, err := json.MarshalIndent(tr, "", " ")
+ if err != nil {
+ return fmt.Sprintf("ToolRequest{error: %v}", err)
+ }
+ return string(data)
+}
+
+// ThinkAggregation
+func (ta ThinkAggregation) IsStop() bool {
+ return ta.Status == Finished && ta.FinalAnswer != ""
+}
+
+func (ta ThinkAggregation) String() string {
+ data, err := json.MarshalIndent(ta, "", " ")
+ if err != nil {
+ return fmt.Sprintf("ThinkAggregation{error: %v}", err)
+ }
+ return string(data)
+}
diff --git a/ai/internal/tools/mock_tools.go b/ai/internal/tools/mock_tools.go
new file mode 100644
index 00000000..d61663b2
--- /dev/null
+++ b/ai/internal/tools/mock_tools.go
@@ -0,0 +1,623 @@
+package tools
+
+import (
+ "fmt"
+ "log"
+
+ "github.com/firebase/genkit/go/ai"
+ "github.com/firebase/genkit/go/genkit"
+)
+
+// ================================================
+// Prometheus Query Service Latency Tool
+// ================================================
+type ToolOutput interface {
+ Tool() string
+}
+
+type PrometheusServiceLatencyInput struct {
+ ServiceName string `json:"serviceName"
jsonschema:"required,description=The name of the service to query"`
+ TimeRangeMinutes int `json:"timeRangeMinutes"
jsonschema:"required,description=Time range in minutes"`
+ Quantile float64 `json:"quantile"
jsonschema:"required,description=Quantile to query (e.g., 0.99 or 0.95)"`
+}
+
+type PrometheusServiceLatencyOutput struct {
+ ToolName string `json:"toolName"`
+ Quantile float64 `json:"quantile"`
+ ValueMillis int `json:"valueMillis"`
+ Summary string `json:"summary"`
+}
+
+func (o PrometheusServiceLatencyOutput) Tool() string {
+ return o.ToolName
+}
+
+func prometheusQueryServiceLatency(ctx *ai.ToolContext, input
PrometheusServiceLatencyInput) (PrometheusServiceLatencyOutput, error) {
+ log.Printf("Tool 'prometheus_query_service_latency' called for service:
%s", input.ServiceName)
+
+ // Mock data based on the example in prompt
+ valueMillis := 3500
+ if input.ServiceName != "order-service" {
+ valueMillis = 850
+ }
+
+ return PrometheusServiceLatencyOutput{
+ ToolName: "prometheus_query_service_latency",
+ Quantile: input.Quantile,
+ ValueMillis: valueMillis,
+ Summary: fmt.Sprintf("服务 %s 在过去%d分钟内的 P%.0f 延迟为 %dms",
input.ServiceName, input.TimeRangeMinutes, input.Quantile*100, valueMillis),
+ }, nil
+}
+
+// ================================================
+// Prometheus Query Service Traffic Tool
+// ================================================
+
+type PrometheusServiceTrafficInput struct {
+ ServiceName string `json:"serviceName"
jsonschema:"required,description=The name of the service to query"`
+ TimeRangeMinutes int `json:"timeRangeMinutes"
jsonschema:"required,description=Time range in minutes"`
+}
+
+type PrometheusServiceTrafficOutput struct {
+ ToolName string `json:"toolName"`
+ RequestRateQPS float64 `json:"requestRateQPS"`
+ ErrorRatePercentage float64 `json:"errorRatePercentage"`
+ Summary string `json:"summary"`
+}
+
+func (o PrometheusServiceTrafficOutput) Tool() string {
+ return o.ToolName
+}
+
+func prometheusQueryServiceTraffic(ctx *ai.ToolContext, input
PrometheusServiceTrafficInput) (PrometheusServiceTrafficOutput, error) {
+ log.Printf("Tool 'prometheus_query_service_traffic' called for service:
%s", input.ServiceName)
+
+ return PrometheusServiceTrafficOutput{
+ ToolName: "prometheus_query_service_traffic",
+ RequestRateQPS: 250.0,
+ ErrorRatePercentage: 5.2,
+ Summary: fmt.Sprintf("服务 %s 的 QPS 为 250, 错误率为
5.2%%", input.ServiceName),
+ }, nil
+}
+
+// ================================================
+// Query Timeseries Database Tool
+// ================================================
+
+type QueryTimeseriesDatabaseInput struct {
+ PromqlQuery string `json:"promqlQuery"
jsonschema:"required,description=PromQL query to execute"`
+}
+
+type TimeseriesMetric struct {
+ Pod string `json:"pod"`
+}
+
+type TimeseriesValue struct {
+ Timestamp int64 `json:"timestamp"`
+ Value string `json:"value"`
+}
+
+type TimeseriesResult struct {
+ Metric TimeseriesMetric `json:"metric"`
+ Value TimeseriesValue `json:"value"`
+}
+
+type QueryTimeseriesDatabaseOutput struct {
+ ToolName string `json:"toolName"`
+ Query string `json:"query"`
+ Results []TimeseriesResult `json:"results"`
+ Summary string `json:"summary"`
+}
+
+func (o QueryTimeseriesDatabaseOutput) Tool() string {
+ return o.ToolName
+}
+
+func queryTimeseriesDatabase(ctx *ai.ToolContext, input
QueryTimeseriesDatabaseInput) (QueryTimeseriesDatabaseOutput, error) {
+ log.Printf("Tool 'query_timeseries_database' called with query: %s",
input.PromqlQuery)
+
+ return QueryTimeseriesDatabaseOutput{
+ ToolName: "query_timeseries_database",
+ Query: input.PromqlQuery,
+ Results: []TimeseriesResult{
+ {
+ Metric: TimeseriesMetric{Pod:
"order-service-pod-1"},
+ Value: TimeseriesValue{Timestamp: 1692192000,
Value: "3.5"},
+ },
+ {
+ Metric: TimeseriesMetric{Pod:
"order-service-pod-2"},
+ Value: TimeseriesValue{Timestamp: 1692192000,
Value: "3.2"},
+ },
+ },
+ Summary: "查询返回了 2 个时间序列",
+ }, nil
+}
+
+// ================================================
+// Application Performance Profiling Tool
+// ================================================
+
+type ApplicationPerformanceProfilingInput struct {
+ ServiceName string `json:"serviceName"
jsonschema:"required,description=The name of the service to profile"`
+ PodName string `json:"podName"
jsonschema:"required,description=The specific pod name to profile"`
+ DurationSeconds int `json:"durationSeconds"
jsonschema:"required,description=Duration of profiling in seconds"`
+}
+
+type PerformanceHotspot struct {
+ CPUTimePercentage float64 `json:"cpuTimePercentage"`
+ StackTrace []string `json:"stackTrace"`
+}
+
+type ApplicationPerformanceProfilingOutput struct {
+ ToolName string `json:"toolName"`
+ Status string `json:"status"`
+ TotalSamples int `json:"totalSamples"`
+ Hotspots []PerformanceHotspot `json:"hotspots"`
+ Summary string `json:"summary"`
+}
+
+func (o ApplicationPerformanceProfilingOutput) Tool() string {
+ return o.ToolName
+}
+
+func applicationPerformanceProfiling(ctx *ai.ToolContext, input
ApplicationPerformanceProfilingInput) (ApplicationPerformanceProfilingOutput,
error) {
+ log.Printf("Tool 'application_performance_profiling' called for
service: %s, pod: %s", input.ServiceName, input.PodName)
+
+ return ApplicationPerformanceProfilingOutput{
+ ToolName: "application_performance_profiling",
+ Status: "completed",
+ TotalSamples: 10000,
+ Hotspots: []PerformanceHotspot{
+ {
+ CPUTimePercentage: 45.5,
+ StackTrace: []string{
+
"com.example.OrderService.processOrder()",
+ "com.example.DatabaseClient.query()",
+
"java.sql.PreparedStatement.executeQuery()",
+ },
+ },
+ },
+ Summary: "性能分析显示,45.5%的CPU时间消耗在数据库查询调用链上",
+ }, nil
+}
+
+// ================================================
+// JVM Performance Analysis Tool
+// ================================================
+
+type JVMPerformanceAnalysisInput struct {
+ ServiceName string `json:"serviceName"
jsonschema:"required,description=The name of the Java service to analyze"`
+ PodName string `json:"podName" jsonschema:"required,description=The
specific pod name to analyze"`
+}
+
+type JVMPerformanceAnalysisOutput struct {
+ ToolName string `json:"toolName"`
+ FullGcCountLastHour int `json:"fullGcCountLastHour"`
+ FullGcTimeAvgMillis int `json:"fullGcTimeAvgMillis"`
+ HeapUsagePercentage float64 `json:"heapUsagePercentage"`
+ Summary string `json:"summary"`
+}
+
+func (o JVMPerformanceAnalysisOutput) Tool() string {
+ return o.ToolName
+}
+
+func jvmPerformanceAnalysis(ctx *ai.ToolContext, input
JVMPerformanceAnalysisInput) (JVMPerformanceAnalysisOutput, error) {
+ log.Printf("Tool 'jvm_performance_analysis' called for service: %s,
pod: %s", input.ServiceName, input.PodName)
+
+ return JVMPerformanceAnalysisOutput{
+ ToolName: "jvm_performance_analysis",
+ FullGcCountLastHour: 15,
+ FullGcTimeAvgMillis: 1200,
+ HeapUsagePercentage: 85.5,
+ Summary: "GC activity is high, average Full GC time
is 1200ms",
+ }, nil
+}
+
+// ================================================
+// Trace Dependency View Tool
+// ================================================
+
+type TraceDependencyViewInput struct {
+ ServiceName string `json:"serviceName"
jsonschema:"required,description=The name of the service to analyze
dependencies"`
+}
+
+type TraceDependencyViewOutput struct {
+ ToolName string `json:"toolName"`
+ UpstreamServices []string `json:"upstreamServices"`
+ DownstreamServices []string `json:"downstreamServices"`
+}
+
+func (o TraceDependencyViewOutput) Tool() string {
+ return o.ToolName
+}
+
+func traceDependencyView(ctx *ai.ToolContext, input TraceDependencyViewInput)
(TraceDependencyViewOutput, error) {
+ log.Printf("Tool 'trace_dependency_view' called for service: %s",
input.ServiceName)
+
+ return TraceDependencyViewOutput{
+ ToolName: "trace_dependency_view",
+ UpstreamServices: []string{"api-gateway", "user-service"},
+ DownstreamServices: []string{"mysql-orders-db", "redis-cache",
"payment-service"},
+ }, nil
+}
+
+// ================================================
+// Trace Latency Analysis Tool
+// ================================================
+
+type TraceLatencyAnalysisInput struct {
+ ServiceName string `json:"serviceName"
jsonschema:"required,description=The name of the service to analyze"`
+ TimeRangeMinutes int `json:"timeRangeMinutes"
jsonschema:"required,description=Time range in minutes"`
+}
+
+type LatencyBottleneck struct {
+ DownstreamService string `json:"downstreamService"`
+ LatencyAvgMillis int `json:"latencyAvgMillis"`
+ ContributionPercentage float64 `json:"contributionPercentage"`
+}
+
+type TraceLatencyAnalysisOutput struct {
+ ToolName string `json:"toolName"`
+ TotalLatencyAvgMillis int `json:"totalLatencyAvgMillis"`
+ Bottlenecks []LatencyBottleneck `json:"bottlenecks"`
+ Summary string `json:"summary"`
+}
+
+func (o TraceLatencyAnalysisOutput) Tool() string {
+ return o.ToolName
+}
+
+func traceLatencyAnalysis(ctx *ai.ToolContext, input
TraceLatencyAnalysisInput) (TraceLatencyAnalysisOutput, error) {
+ log.Printf("Tool 'trace_latency_analysis' called for service: %s",
input.ServiceName)
+
+ return TraceLatencyAnalysisOutput{
+ ToolName: "trace_latency_analysis",
+ TotalLatencyAvgMillis: 3200,
+ Bottlenecks: []LatencyBottleneck{
+ {
+ DownstreamService: "mysql-orders-db",
+ LatencyAvgMillis: 3050,
+ ContributionPercentage: 95.3,
+ },
+ {
+ DownstreamService: "user-service",
+ LatencyAvgMillis: 150,
+ ContributionPercentage: 4.7,
+ },
+ },
+ Summary: "平均总延迟为 3200ms。瓶颈已定位,95.3% 的延迟来自对下游 'mysql-orders-db'
的调用",
+ }, nil
+}
+
+// ================================================
+// Database Connection Pool Analysis Tool
+// ================================================
+
+type DatabaseConnectionPoolAnalysisInput struct {
+ ServiceName string `json:"serviceName"
jsonschema:"required,description=The name of the service to analyze"`
+}
+
+type DatabaseConnectionPoolAnalysisOutput struct {
+ ToolName string `json:"toolName"`
+ MaxConnections int `json:"maxConnections"`
+ ActiveConnections int `json:"activeConnections"`
+ IdleConnections int `json:"idleConnections"`
+ PendingRequests int `json:"pendingRequests"`
+ Summary string `json:"summary"`
+}
+
+func (o DatabaseConnectionPoolAnalysisOutput) Tool() string {
+ return o.ToolName
+}
+
+func databaseConnectionPoolAnalysis(ctx *ai.ToolContext, input
DatabaseConnectionPoolAnalysisInput) (DatabaseConnectionPoolAnalysisOutput,
error) {
+ log.Printf("Tool 'database_connection_pool_analysis' called for
service: %s", input.ServiceName)
+
+ return DatabaseConnectionPoolAnalysisOutput{
+ ToolName: "database_connection_pool_analysis",
+ MaxConnections: 100,
+ ActiveConnections: 100,
+ IdleConnections: 0,
+ PendingRequests: 58,
+ Summary: "数据库连接池已完全耗尽 (100/100),当前有 58 个请求正在排队等待连接",
+ }, nil
+}
+
+// ================================================
+// Kubernetes Get Pod Resources Tool
+// ================================================
+
+type KubernetesGetPodResourcesInput struct {
+ ServiceName string `json:"serviceName"
jsonschema:"required,description=The name of the service"`
+ Namespace string `json:"namespace"
jsonschema:"required,description=The namespace of the service"`
+}
+
+type PodResource struct {
+ PodName string `json:"podName"`
+ CPUUsageCores float64 `json:"cpuUsageCores"`
+ CPURequestCores float64 `json:"cpuRequestCores"`
+ CPULimitCores float64 `json:"cpuLimitCores"`
+ MemoryUsageMi int `json:"memoryUsageMi"`
+ MemoryRequestMi int `json:"memoryRequestMi"`
+ MemoryLimitMi int `json:"memoryLimitMi"`
+}
+
+type KubernetesGetPodResourcesOutput struct {
+ ToolName string `json:"toolName"`
+ Pods []PodResource `json:"pods"`
+ Summary string `json:"summary"`
+}
+
+func (o KubernetesGetPodResourcesOutput) Tool() string {
+ return o.ToolName
+}
+
+func kubernetesGetPodResources(ctx *ai.ToolContext, input
KubernetesGetPodResourcesInput) (KubernetesGetPodResourcesOutput, error) {
+ log.Printf("Tool 'kubernetes_get_pod_resources' called for service: %s
in namespace: %s", input.ServiceName, input.Namespace)
+
+ return KubernetesGetPodResourcesOutput{
+ ToolName: "kubernetes_get_pod_resources",
+ Pods: []PodResource{
+ {
+ PodName: "order-service-pod-1",
+ CPUUsageCores: 0.8,
+ CPURequestCores: 0.5,
+ CPULimitCores: 1.0,
+ MemoryUsageMi: 1800,
+ MemoryRequestMi: 1024,
+ MemoryLimitMi: 2048,
+ },
+ {
+ PodName: "order-service-pod-2",
+ CPUUsageCores: 0.9,
+ CPURequestCores: 0.5,
+ CPULimitCores: 1.0,
+ MemoryUsageMi: 1950,
+ MemoryRequestMi: 1024,
+ MemoryLimitMi: 2048,
+ },
+ },
+ Summary: "2 out of 2 pods are near their memory limits",
+ }, nil
+}
+
+// ================================================
+// Dubbo Service Status Tool
+// ================================================
+
+type DubboServiceStatusInput struct {
+ ServiceName string `json:"serviceName"
jsonschema:"required,description=The name of the Dubbo service"`
+}
+
+type DubboProvider struct {
+ IP string `json:"ip"`
+ Port int `json:"port"`
+ Status string `json:"status"`
+}
+
+type DubboConsumer struct {
+ IP string `json:"ip"`
+ Application string `json:"application"`
+ Status string `json:"status"`
+}
+
+type DubboServiceStatusOutput struct {
+ ToolName string `json:"toolName"`
+ Providers []DubboProvider `json:"providers"`
+ Consumers []DubboConsumer `json:"consumers"`
+}
+
+func (o DubboServiceStatusOutput) Tool() string {
+ return o.ToolName
+}
+
+func dubboServiceStatus(ctx *ai.ToolContext, input DubboServiceStatusInput)
(DubboServiceStatusOutput, error) {
+ log.Printf("Tool 'dubbo_service_status' called for service: %s",
input.ServiceName)
+
+ return DubboServiceStatusOutput{
+ ToolName: "dubbo_service_status",
+ Providers: []DubboProvider{
+ {IP: "192.168.1.10", Port: 20880, Status: "healthy"},
+ {IP: "192.168.1.11", Port: 20880, Status: "healthy"},
+ },
+ Consumers: []DubboConsumer{
+ {IP: "192.168.1.20", Application: "web-frontend",
Status: "connected"},
+ {IP: "192.168.1.21", Application: "api-gateway",
Status: "connected"},
+ },
+ }, nil
+}
+
+// ================================================
+// Query Log Database Tool
+// ================================================
+
+type QueryLogDatabaseInput struct {
+ ServiceName string `json:"serviceName"
jsonschema:"required,description=The name of the service"`
+ Keyword string `json:"keyword"
jsonschema:"required,description=Keyword to search for"`
+ TimeRangeMinutes int `json:"timeRangeMinutes"
jsonschema:"required,description=Time range in minutes"`
+}
+
+type LogEntry struct {
+ Timestamp string `json:"timestamp"`
+ Level string `json:"level"`
+ Message string `json:"message"`
+}
+
+type QueryLogDatabaseOutput struct {
+ ToolName string `json:"toolName"`
+ TotalHits int `json:"totalHits"`
+ Logs []LogEntry `json:"logs"`
+ Summary string `json:"summary"`
+}
+
+func (o QueryLogDatabaseOutput) Tool() string {
+ return o.ToolName
+}
+
+func queryLogDatabase(ctx *ai.ToolContext, input QueryLogDatabaseInput)
(QueryLogDatabaseOutput, error) {
+ log.Printf("Tool 'query_log_database' called for service: %s, keyword:
%s", input.ServiceName, input.Keyword)
+
+ return QueryLogDatabaseOutput{
+ ToolName: "query_log_database",
+ TotalHits: 152,
+ Logs: []LogEntry{
+ {
+ Timestamp: "2025-08-16T15:32:05Z",
+ Level: "WARN",
+ Message: "Timeout waiting for idle object in
database connection pool.",
+ },
+ {
+ Timestamp: "2025-08-16T15:32:08Z",
+ Level: "WARN",
+ Message: "Timeout waiting for idle object in
database connection pool.",
+ },
+ },
+ Summary: fmt.Sprintf("在过去%d分钟内,发现 152 条关于 '%s' 的日志条目",
input.TimeRangeMinutes, input.Keyword),
+ }, nil
+}
+
+// ================================================
+// Search Archived Logs Tool
+// ================================================
+
+type SearchArchivedLogsInput struct {
+ FilePathPattern string `json:"filePathPattern"
jsonschema:"required,description=File path pattern to search"`
+ GrepKeyword string `json:"grepKeyword"
jsonschema:"required,description=Keyword to grep for"`
+}
+
+type MatchingLine struct {
+ FilePath string `json:"filePath"`
+ LineNumber int `json:"lineNumber"`
+ LineContent string `json:"lineContent"`
+}
+
+type SearchArchivedLogsOutput struct {
+ ToolName string `json:"toolName"`
+ FilesSearched int `json:"filesSearched"`
+ MatchingLines []MatchingLine `json:"matchingLines"`
+ Summary string `json:"summary"`
+}
+
+func (o SearchArchivedLogsOutput) Tool() string {
+ return o.ToolName
+}
+
+func searchArchivedLogs(ctx *ai.ToolContext, input SearchArchivedLogsInput)
(SearchArchivedLogsOutput, error) {
+ log.Printf("Tool 'search_archived_logs' called with pattern: %s,
keyword: %s", input.FilePathPattern, input.GrepKeyword)
+
+ return SearchArchivedLogsOutput{
+ ToolName: "search_archived_logs",
+ FilesSearched: 5,
+ MatchingLines: []MatchingLine{
+ {
+ FilePath:
"/logs/mysql-orders-db/slow-query-2025-08-16.log.gz",
+ LineNumber: 1024,
+ LineContent: "Query_time: 25.3s | SELECT
COUNT(id), SUM(price) FROM orders WHERE user_id = 'VIP_USER_123';",
+ },
+ {
+ FilePath:
"/logs/mysql-orders-db/slow-query-2025-08-16.log.gz",
+ LineNumber: 1029,
+ LineContent: "Query_time: 28.1s | SELECT
COUNT(id), SUM(price) FROM orders WHERE user_id = 'VIP_USER_456';",
+ },
+ },
+ Summary: "在归档的日志文件中,发现了多条查询,搜索了 5 个文件,找到了 2 条匹配行",
+ }, nil
+}
+
+// ================================================
+// Query Knowledge Base Tool
+// ================================================
+
+type QueryKnowledgeBaseInput struct {
+ QueryText string `json:"queryText"
jsonschema:"required,description=Query text to search for"`
+}
+
+type KnowledgeDocument struct {
+ Source string `json:"source"`
+ ContentSnippet string `json:"contentSnippet"`
+ SimilarityScore float64 `json:"similarityScore"`
+}
+
+type QueryKnowledgeBaseOutput struct {
+ ToolName string `json:"toolName"`
+ Documents []KnowledgeDocument `json:"documents"`
+}
+
+func (o QueryKnowledgeBaseOutput) Tool() string {
+ return o.ToolName
+}
+
+func queryKnowledgeBase(ctx *ai.ToolContext, input QueryKnowledgeBaseInput)
(QueryKnowledgeBaseOutput, error) {
+ log.Printf("Tool 'query_knowledge_base' called with query: %s",
input.QueryText)
+
+ return QueryKnowledgeBaseOutput{
+ ToolName: "query_knowledge_base",
+ Documents: []KnowledgeDocument{
+ {
+ Source:
"Project-VIP-Feature-Design-Doc.md",
+ ContentSnippet: "The 'Lifetime Achievement'
badge requires calculating total user spending. Note: This may cause slow
queries on the orders table if the user_id column is not properly indexed.",
+ SimilarityScore: 0.92,
+ },
+ },
+ }, nil
+}
+
+// ================================================
+// Tool Registration Function
+// ================================================
+
+var registeredTools []ai.Tool
+
+// RegisterAllMockTools registers all mock diagnostic tools with the genkit
instance
+func RegisterAllMockTools(g *genkit.Genkit) {
+ registeredTools = []ai.Tool{
+ genkit.DefineTool(g, "prometheus_query_service_latency",
"查询指定服务在特定时间范围内的 P95/P99 延迟指标", prometheusQueryServiceLatency),
+ genkit.DefineTool(g, "prometheus_query_service_traffic",
"查询指定服务在特定时间范围内的请求率 (QPS) 和错误率 (Error Rate)", prometheusQueryServiceTraffic),
+ genkit.DefineTool(g, "query_timeseries_database", "执行一条完整的
PromQL 查询语句,用于进行普罗米修斯历史数据的深度或自定义分析", queryTimeseriesDatabase),
+ genkit.DefineTool(g, "application_performance_profiling",
"对指定服务的指定实例(Pod)进行性能剖析,以结构化文本格式返回消耗CPU最多的函数调用栈",
applicationPerformanceProfiling),
+ genkit.DefineTool(g, "jvm_performance_analysis",
"检查指定Java服务的JVM状态,特别是GC(垃圾回收)活动", jvmPerformanceAnalysis),
+ genkit.DefineTool(g, "trace_dependency_view",
"基于链路追踪数据,查询指定服务的上下游依赖关系", traceDependencyView),
+ genkit.DefineTool(g, "trace_latency_analysis",
"分析指定服务在某时间范围内的链路追踪数据,定位延迟最高的下游调用", traceLatencyAnalysis),
+ genkit.DefineTool(g, "database_connection_pool_analysis",
"查询指定服务连接数据库的连接池状态", databaseConnectionPoolAnalysis),
+ genkit.DefineTool(g, "kubernetes_get_pod_resources", "使用类似
kubectl 的功能,获取指定服务所有Pod的CPU和内存的静态配置(Limits/Requests)和动态使用情况",
kubernetesGetPodResources),
+ genkit.DefineTool(g, "dubbo_service_status", "使用类似 dubbo-admin
的命令,查询指定Dubbo服务的提供者和消费者列表及其状态", dubboServiceStatus),
+ genkit.DefineTool(g, "query_log_database",
"查询已索引的日志数据库(如Elasticsearch, Loki),用于实时或近实时的日志分析", queryLogDatabase),
+ genkit.DefineTool(g, "search_archived_logs",
"在归档的日志文件(如存储在S3或服务器文件系统的.log.gz文件)中进行文本搜索(类似grep)", searchArchivedLogs),
+ genkit.DefineTool(g, "query_knowledge_base",
"在向量数据库中查询与问题相关的历史故障报告或解决方案文档", queryKnowledgeBase),
+ }
+
+ log.Printf("Registered %d mock diagnostic tools", len(registeredTools))
+}
+
+func AllMockToolRef() (toolRef []ai.ToolRef, err error) {
+ if registeredTools == nil {
+ return nil, fmt.Errorf("no mock tools registered")
+ }
+ for _, tool := range registeredTools {
+ toolRef = append(toolRef, tool)
+ }
+ return toolRef, nil
+}
+
+func AllMockToolNames() (toolNames map[string]struct{}, err error) {
+ if registeredTools == nil {
+ return nil, fmt.Errorf("no mock tools registered")
+ }
+ toolNames = make(map[string]struct{})
+ for _, tool := range registeredTools {
+ toolNames[tool.Name()] = struct{}{}
+ }
+ return toolNames, nil
+}
+
+func MockToolRefByNames(names []string) (toolRefs []ai.ToolRef) {
+ for _, name := range names {
+ for _, tool := range registeredTools {
+ if tool.Name() == name {
+ toolRefs = append(toolRefs, tool)
+ }
+ }
+ }
+ return toolRefs
+}
diff --git a/ai/internal/tools/tools.go b/ai/internal/tools/tools.go
new file mode 100644
index 00000000..17a7bded
--- /dev/null
+++ b/ai/internal/tools/tools.go
@@ -0,0 +1,39 @@
+package tools
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "github.com/firebase/genkit/go/genkit"
+)
+
+// type ToolOut interface {
+// FromRaw(any) ToolOutput
+// Output() string
+// }
+
+// func (s StringOutput) Output() string {
+// return s.value
+// }
+
+// ToolDesc 表示要执行的工具调用信息
+type ToolDesc struct {
+ ToolName string `json:"tool_name"`
+ ToolInput map[string]any `json:"tool_input"`
+}
+
+func (toolDesc ToolDesc) Call(g *genkit.Genkit, ctx context.Context) (string,
error) {
+ tool := genkit.LookupTool(g, toolDesc.ToolName)
+ if tool == nil {
+ return "", fmt.Errorf("tool not found: %s", toolDesc.ToolName)
+ }
+
+ rawToolOutput, err := tool.RunRaw(ctx, toolDesc.ToolInput)
+ if err != nil {
+ return "", fmt.Errorf("failed to call tool %s: %w",
toolDesc.ToolName, err)
+ }
+
+ jsonOutput, err := json.Marshal(rawToolOutput)
+ return string(jsonOutput), err
+}
diff --git a/ai/main.go b/ai/main.go
new file mode 100644
index 00000000..9177148b
--- /dev/null
+++ b/ai/main.go
@@ -0,0 +1,13 @@
+package main
+
+import (
+ "dubbo-admin-ai/internal/agent"
+ "dubbo-admin-ai/plugins/dashscope"
+)
+
+func main() {
+ if err := agent.InitAgent(dashscope.Qwen3); err != nil {
+ panic(err)
+ }
+ select {}
+}
diff --git a/ai/plugins/dashscope/dashscope.go
b/ai/plugins/dashscope/dashscope.go
new file mode 100644
index 00000000..df838530
--- /dev/null
+++ b/ai/plugins/dashscope/dashscope.go
@@ -0,0 +1,148 @@
+package dashscope
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/firebase/genkit/go/ai"
+ "github.com/firebase/genkit/go/core"
+ "github.com/firebase/genkit/go/genkit"
+ "github.com/firebase/genkit/go/plugins/compat_oai"
+ "github.com/openai/openai-go/option"
+)
+
+const provider = "dashscope"
+const baseURL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
+
+const (
+ qwen3_235b_a22b = "qwen3-235b-a22b-instruct-2507"
+ qwen_max = "qwen-max"
+ qwen_plus = "qwen-plus"
+ qwen_flash = "qwen-flash"
+ qwen3_coder = "qwen3-coder-plus"
+
+ Qwen3 = provider + "/" + qwen3_235b_a22b
+ Qwen_plus = provider + "/" + qwen_plus
+ Qwen_max = provider + "/" + qwen_max
+ Qwen3_coder = provider + "/" + qwen3_coder
+ Qwen_flash = provider + "/" + qwen_flash
+)
+
+var (
+ supportedModels = map[string]ai.ModelInfo{
+ qwen3_235b_a22b: {
+ Label: "qwen3-235b-a22b-instruct-2507",
+ Supports: &compat_oai.BasicText,
+ Versions: []string{"qwen3-235b-a22b-instruct-2507"},
+ },
+ qwen_plus: {
+ Label: "qwen-plus",
+ Supports: &compat_oai.BasicText,
+ Versions: []string{"qwen-plus"},
+ },
+ qwen_max: {
+ Label: "qwen-max",
+ Supports: &compat_oai.BasicText,
+ Versions: []string{"qwen-max"},
+ },
+ qwen3_coder: {
+ Label: "qwen3-coder",
+ Supports: &compat_oai.BasicText,
+ Versions: []string{"qwen3-coder"},
+ },
+ qwen_flash: {
+ Label: "qwen-flash",
+ Supports: &compat_oai.BasicText,
+ Versions: []string{"qwen-flash"},
+ },
+ }
+
+ knownEmbedders = []string{}
+)
+
+type DashScope struct {
+ APIKey string
+
+ Opts []option.RequestOption
+
+ openAICompatible *compat_oai.OpenAICompatible
+}
+
+// Name implements genkit.Plugin.
+func (o *DashScope) Name() string {
+ return provider
+}
+
+// Init implements genkit.Plugin.
+func (o *DashScope) Init(ctx context.Context, g *genkit.Genkit) error {
+ apiKey := o.APIKey
+
+ // if api key is not set, get it from environment variable
+ if apiKey == "" {
+ apiKey = os.Getenv("DASHSCOPE_API_KEY")
+ }
+
+ if apiKey == "" {
+ return fmt.Errorf("DashScope plugin initialization failed:
apiKey is required")
+ }
+
+ if o.openAICompatible == nil {
+ o.openAICompatible = &compat_oai.OpenAICompatible{}
+ }
+
+ // set the options
+ o.openAICompatible.Opts = []option.RequestOption{
+ option.WithAPIKey(apiKey),
+ option.WithBaseURL(baseURL),
+ }
+
+ if len(o.Opts) > 0 {
+ o.openAICompatible.Opts = append(o.openAICompatible.Opts,
o.Opts...)
+ }
+
+ o.openAICompatible.Provider = provider
+ if err := o.openAICompatible.Init(ctx, g); err != nil {
+ return err
+ }
+
+ // define default models
+ for model, info := range supportedModels {
+ if _, err := o.DefineModel(g, model, info); err != nil {
+ return err
+ }
+ }
+
+ // define default embedders
+ for _, embedder := range knownEmbedders {
+ if _, err := o.DefineEmbedder(g, embedder); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (o *DashScope) Model(g *genkit.Genkit, name string) ai.Model {
+ return o.openAICompatible.Model(g, name, provider)
+}
+
+func (o *DashScope) DefineModel(g *genkit.Genkit, name string, info
ai.ModelInfo) (ai.Model, error) {
+ return o.openAICompatible.DefineModel(g, provider, name, info)
+}
+
+func (o *DashScope) DefineEmbedder(g *genkit.Genkit, name string)
(ai.Embedder, error) {
+ return o.openAICompatible.DefineEmbedder(g, provider, name)
+}
+
+func (o *DashScope) Embedder(g *genkit.Genkit, name string) ai.Embedder {
+ return o.openAICompatible.Embedder(g, name, provider)
+}
+
+func (o *DashScope) ListActions(ctx context.Context) []core.ActionDesc {
+ return o.openAICompatible.ListActions(ctx)
+}
+
+func (o *DashScope) ResolveAction(g *genkit.Genkit, atype core.ActionType,
name string) error {
+ return o.openAICompatible.ResolveAction(g, atype, name)
+}
diff --git a/ai/plugins/siliconflow/siliconflow.go
b/ai/plugins/siliconflow/siliconflow.go
new file mode 100644
index 00000000..00597baf
--- /dev/null
+++ b/ai/plugins/siliconflow/siliconflow.go
@@ -0,0 +1,141 @@
+package siliconflow
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/firebase/genkit/go/ai"
+ "github.com/firebase/genkit/go/core"
+ "github.com/firebase/genkit/go/genkit"
+ "github.com/firebase/genkit/go/plugins/compat_oai"
+ "github.com/openai/openai-go/option"
+)
+
+const provider = "siliconflow"
+const baseURL = "https://api.siliconflow.cn/v1"
+
+const (
+ deepseekV3 = "deepseek-ai/DeepSeek-V3"
+ deepseekR1 = "deepseek-ai/DeepSeek-R1"
+ qwenQwQ32B = "Qwen/QwQ-32B"
+ qwen3Coder = "Qwen/Qwen3-Coder-480B-A35B-Instruct"
+
+ DeepSeekV3 = provider + "/" + deepseekV3
+ QwenQwQ32B = provider + "/" + qwenQwQ32B
+ Qwen3Coder = provider + "/" + qwen3Coder
+ DeepSeekR1 = provider + "/" + deepseekR1
+)
+
+var (
+ supportedModels = map[string]ai.ModelInfo{
+ deepseekV3: {
+ Label: "deepseek-ai/DeepSeek-V3",
+ Supports: &compat_oai.BasicText,
+ Versions: []string{"DeepSeek-V3-0324"},
+ },
+ qwen3Coder: {
+ Label: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
+ Supports: &compat_oai.Multimodal,
+ Versions: []string{"Qwen3-Coder-480B-A35B"},
+ },
+ qwenQwQ32B: {
+ Label: "Qwen/QwQ-32B",
+ Supports: &compat_oai.BasicText,
+ Versions: []string{"QwQ-32B"},
+ },
+ deepseekR1: {
+ Label: "deepseek-ai/DeepSeek-R1",
+ Supports: &compat_oai.BasicText,
+ Versions: []string{"DeepSeek-R1-0528"},
+ },
+ }
+
+ knownEmbedders = []string{}
+)
+
+type SiliconFlow struct {
+ APIKey string
+
+ Opts []option.RequestOption
+
+ openAICompatible *compat_oai.OpenAICompatible
+}
+
+// Name implements genkit.Plugin.
+func (o *SiliconFlow) Name() string {
+ return provider
+}
+
+// Init implements genkit.Plugin.
+func (o *SiliconFlow) Init(ctx context.Context, g *genkit.Genkit) error {
+ apiKey := o.APIKey
+
+ // if api key is not set, get it from environment variable
+ if apiKey == "" {
+ apiKey = os.Getenv("SILICONFLOW_API_KEY")
+ }
+
+ if apiKey == "" {
+ return fmt.Errorf("siliconflow plugin initialization failed:
apiKey is required")
+ }
+
+ if o.openAICompatible == nil {
+ o.openAICompatible = &compat_oai.OpenAICompatible{}
+ }
+
+ // set the options
+ o.openAICompatible.Opts = []option.RequestOption{
+ option.WithAPIKey(apiKey),
+ option.WithBaseURL(baseURL),
+ }
+
+ if len(o.Opts) > 0 {
+ o.openAICompatible.Opts = append(o.openAICompatible.Opts,
o.Opts...)
+ }
+
+ o.openAICompatible.Provider = provider
+ if err := o.openAICompatible.Init(ctx, g); err != nil {
+ return err
+ }
+
+ // define default models
+ for model, info := range supportedModels {
+ if _, err := o.DefineModel(g, model, info); err != nil {
+ return err
+ }
+ }
+
+ // define default embedders
+ for _, embedder := range knownEmbedders {
+ if _, err := o.DefineEmbedder(g, embedder); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (o *SiliconFlow) Model(g *genkit.Genkit, name string) ai.Model {
+ return o.openAICompatible.Model(g, name, provider)
+}
+
+func (o *SiliconFlow) DefineModel(g *genkit.Genkit, name string, info
ai.ModelInfo) (ai.Model, error) {
+ return o.openAICompatible.DefineModel(g, provider, name, info)
+}
+
+func (o *SiliconFlow) DefineEmbedder(g *genkit.Genkit, name string)
(ai.Embedder, error) {
+ return o.openAICompatible.DefineEmbedder(g, provider, name)
+}
+
+func (o *SiliconFlow) Embedder(g *genkit.Genkit, name string) ai.Embedder {
+ return o.openAICompatible.Embedder(g, name, provider)
+}
+
+func (o *SiliconFlow) ListActions(ctx context.Context) []core.ActionDesc {
+ return o.openAICompatible.ListActions(ctx)
+}
+
+func (o *SiliconFlow) ResolveAction(g *genkit.Genkit, atype core.ActionType,
name string) error {
+ return o.openAICompatible.ResolveAction(g, atype, name)
+}
diff --git a/ai/prompts/agentSystem.prompt b/ai/prompts/agentSystem.prompt
new file mode 100644
index 00000000..b5509ff6
--- /dev/null
+++ b/ai/prompts/agentSystem.prompt
@@ -0,0 +1,90 @@
+# 1. 角色设定 (Persona)
+You are "Dubbot", the **reasoning brain** of a ReAct (Reasoning and Acting)
agent system. You are an expert Site Reliability Engineer (SRE) and
observability specialist integrated within the Dubbo Admin control plane. Your
sole responsibility is **thinking and decision-making** - you analyze
information, reason about problems, and decide what actions should be taken
next.
+
+You do NOT execute tools or actions yourself. You are the strategic thinker
who guides the problem-solving process.
+
+# 2. ReAct 核心流程 (ReAct Core Process)
+You operate within the **ReAct paradigm**: **Reasoning → Acting → Observing →
Reasoning...**
+
+**Your role in this cycle:**
+1. **THOUGHT (思考)**: Analyze current information and reason about the problem
+2. **DECISION (决策)**: Decide what actions/tools should be executed next (can
be multiple parallel tools for efficiency), or if enough information exists to
provide a final answer
+3. **ITERATION (迭代)**: Continue this cycle until the problem is fully diagnosed
+
+**You receive**: User questions + Historical observations from previous tool
executions
+**You output**: Your reasoning process + Next action decision OR final
diagnosis
+
+# 3. ReAct 思维框架 (ReAct Thinking Framework)
+For each reasoning cycle, follow this structured approach:
+
+## **Step 1: 状态分析 (State Analysis)**
+- What do I know so far from the user question and previous observations?
+- What patterns or anomalies have been identified?
+- What hypotheses have been formed or ruled out?
+
+## **Step 2: 问题分解 (Problem Decomposition)**
+- What are the possible root causes for this issue?
+- What aspects of the system need to be investigated?
+- Which investigation path is most likely to yield useful information?
+
+## **Step 3: 信息差距识别 (Information Gap Analysis)**
+- What critical information am I still missing?
+- Which data points would help confirm or refute my hypotheses?
+- What independent information sources can be queried in parallel for
efficiency?
+- What is the most efficient approach to gather needed information (single vs
multiple tools)?
+
+## **Step 4: 行动决策 (Action Decision)**
+- Should I gather more data (status="CONTINUED") or provide final answer
(status="FINISHED")?
+- If gathering data: Which specific tools should be used and with what
parameters?
+- Can multiple tools be executed in parallel to improve efficiency (no data
dependencies)?
+- If final answer: Do I have sufficient evidence to make a confident diagnosis?
+
+# 4. 可用工具与数据源 (Available Tools & Data Sources)
+You can request data from these sources through tool calls:
+- **Metrics Tools**: Performance indicators (latency, QPS, error rates,
resource usage)
+- **Logging Tools**: Application logs from all services (INFO, WARN, ERROR
levels)
+- **Tracing Tools**: Distributed traces showing request lifecycle across
services
+- **Metadata Tools**: Service dependencies, instance information,
configurations
+- **Knowledge Base Tools**: Historical incidents, troubleshooting guides, best
practices
+
+## 并行工具执行原则 (Parallel Tool Execution Guidelines)
+**Execute multiple tools in parallel when:**
+- Information sources are independent (no data dependencies between tools)
+- Different aspects of the system need investigation simultaneously
+- Multiple hypotheses can be validated concurrently
+- Efficiency gains outweigh coordination complexity
+
+**Use single tool execution when:**
+- Results from one tool determine parameters for the next tool
+- Sequential analysis is required (e.g., trace analysis → log filtering)
+- System resources are limited or tools have mutual exclusion requirements
+
+# 5. 输出决策逻辑 (Output Decision Logic)
+**Continue Investigating (status="CONTINUED")** when:
+- Key information is missing for diagnosis
+- Multiple hypotheses need validation (consider parallel tool execution)
+- Current evidence is insufficient or contradictory
+- Independent data sources can be queried simultaneously for efficiency
+
+**Provide Final Answer (status="FINISHED")** when:
+- Root cause is clearly identified with supporting evidence
+- Sufficient data has been gathered to make confident diagnosis
+- Clear troubleshooting steps can be recommended
+
+**Important**: final_answer field must always be a string type:
+- When there is a final answer: Provide specific diagnosis and recommendation
content
+- When there is no final answer: Use empty string "" (do not use null)
+
+# 6. ReAct 思考要求 (ReAct Thinking Requirements)
+1. **Be Systematic**: Follow the thinking framework step by step
+2. **Be Evidence-Based**: Ground all reasoning in observed data
+3. **Be Iterative**: Treat each cycle as building upon previous observations
+4. **Be Strategic**: Choose the most informative actions (single or multiple
tools as appropriate)
+5. **Be Efficient**: Prefer parallel tool execution when data sources are
independent
+6. **Be Decisive**: Know when you have enough information to conclude
+
+**Your output MUST be a single valid JSON object following the specified
schema.**
+
+**Field Type Requirements**: All string fields must be valid strings, never
use null values. Use empty string "" for final_answer when no content.
+
+**Remember: You are the BRAIN of the ReAct agent. Think deeply, reason
carefully, and guide the investigation process strategically.**
\ No newline at end of file
diff --git a/ai/prompts/menuPrompt.prompt b/ai/prompts/menuPrompt.prompt
new file mode 100644
index 00000000..61faf580
--- /dev/null
+++ b/ai/prompts/menuPrompt.prompt
@@ -0,0 +1,15 @@
+---
+input:
+ schema:
+ theme?: string
+ default:
+ theme: "pirate"
+output:
+ schema:
+ dishname: string
+ description: string
+ calories: integer
+ allergens(array): string
+---
+Invent a menu item for a {{theme}} themed
+restaurant.
\ No newline at end of file
diff --git a/ai/prompts/weatherPrompt.prompt b/ai/prompts/weatherPrompt.prompt
new file mode 100644
index 00000000..105ed3a4
--- /dev/null
+++ b/ai/prompts/weatherPrompt.prompt
@@ -0,0 +1,12 @@
+---
+system: "Answer questions using the tools you have."
+tools: [getWeather]
+input:
+ schema:
+ location: string
+output:
+ schema:
+ type: string
+---
+
+What is the weather in {{location}}?
\ No newline at end of file
diff --git a/ai/test/flow_test.go b/ai/test/flow_test.go
new file mode 100644
index 00000000..39c6c3b6
--- /dev/null
+++ b/ai/test/flow_test.go
@@ -0,0 +1,107 @@
+package test
+
+import (
+ "context"
+ "dubbo-admin-ai/internal/agent"
+ "dubbo-admin-ai/internal/schema"
+ "encoding/json"
+ "fmt"
+ "testing"
+)
+
+func TestThinking(t *testing.T) {
+ ctx := context.Background()
+ agentInput := schema.ReActInput{
+ UserInput: "我的微服务 order-service 运行缓慢,请帮助我诊断原因",
+ }
+
+ resp, err := agent.ThinkingFlow.Run(ctx, agentInput)
+ if err != nil {
+ t.Fatalf("failed to run thinking flow: %v", err)
+ }
+ if resp == nil {
+ t.Fatal("expected non-nil response")
+ }
+
+ fmt.Print(resp)
+}
+
+func TestAct(t *testing.T) {
+ ctx := context.Background()
+
+ actInJson := `{
+ "tool_requests": [
+ {
+ "thought": "查询order-service的P95和P99延迟指标,以确认性能问题的严重程度。",
+ "tool_desc": {
+ "tool_name": "prometheus_query_service_latency",
+ "tool_input": {
+ "quantile": 0.95,
+ "serviceName": "order-service",
+ "timeRangeMinutes": 10
+ }
+ }
+ },
+ {
+ "thought": "查询order-service的QPS和错误率,评估服务负载和健康状态。",
+ "tool_desc": {
+ "tool_name": "prometheus_query_service_traffic",
+ "tool_input": {
+ "serviceName": "order-service",
+ "timeRangeMinutes": 10
+ }
+ }
+ },
+ {
+ "thought": "分析order-service的链路追踪数据,找出延迟最高的下游调用。",
+ "tool_desc": {
+ "tool_name": "trace_latency_analysis",
+ "tool_input": {
+ "serviceName": "order-service",
+ "timeRangeMinutes": 10
+ }
+ }
+ },
+ {
+ "thought": "检查order-service的Dubbo提供者和消费者列表及其状态,确认服务依赖是否正常。",
+ "tool_desc": {
+ "tool_name": "dubbo_service_status",
+ "tool_input": {
+ "serviceName": "order-service"
+ }
+ }
+ }
+ ],
+ "status": "CONTINUED",
+ "thought":
"初步分析表明order-service的性能问题可能有多个潜在原因,包括高延迟的下游调用、资源不足、JVM问题或数据库连接池问题等。首先需要收集系统性能数据,以便进一步诊断。"
+}`
+ actIn := &schema.ThinkAggregation{}
+ if err := json.Unmarshal([]byte(actInJson), actIn); err != nil {
+ t.Fatalf("failed to unmarshal actInJson: %v", err)
+ }
+
+ resp, err := agent.ActFlow.Run(ctx, actIn)
+ if err != nil {
+ t.Fatalf("failed to run act flow: %v", err)
+ }
+ if resp == nil {
+ t.Fatal("expected non-nil response")
+ }
+
+ for _, r := range resp {
+ fmt.Println(r)
+ }
+}
+
+func TestReAct(t *testing.T) {
+ ctx := context.Background()
+ agentInput := schema.ReActInput{
+ UserInput: "我的微服务 order-service 运行缓慢,请帮助我诊断原因",
+ }
+
+ reActResp, err := agent.ReActFlow.Run(ctx, agentInput)
+ if err != nil {
+ t.Fatalf("failed to run reAct flow: %v", err)
+ }
+ fmt.Println(reActResp)
+}
diff --git a/ai/test/llm_test.go b/ai/test/llm_test.go
new file mode 100644
index 00000000..b2081eaa
--- /dev/null
+++ b/ai/test/llm_test.go
@@ -0,0 +1,66 @@
+package test
+
+import (
+ "context"
+ "dubbo-admin-ai/internal/manager"
+ "fmt"
+ "log"
+ "testing"
+
+ "github.com/firebase/genkit/go/ai"
+ "github.com/firebase/genkit/go/core"
+ "github.com/firebase/genkit/go/genkit"
+)
+
+type WeatherInput struct {
+ Location string `json:"location" jsonschema_description:"Location to
get weather for"`
+}
+
+func defineWeatherFlow(g *genkit.Genkit) *core.Flow[WeatherInput, string,
struct{}] {
+ getWeatherTool := genkit.DefineTool(g, "getWeather", "Gets the current
weather in a given location",
+ func(ctx *ai.ToolContext, input WeatherInput) (string, error) {
+ // Here, we would typically make an API call or
database query. For this
+ // example, we just return a fixed value.
+ log.Printf("Tool 'getWeather' called for location: %s",
input.Location)
+ return fmt.Sprintf("The current weather in %s is 63°F
and sunny.", input.Location), nil
+ })
+
+ return genkit.DefineFlow(g, "getWeatherFlow",
+ func(ctx context.Context, location WeatherInput) (string,
error) {
+ resp, err := genkit.Generate(ctx, g,
+ ai.WithTools(getWeatherTool),
+ ai.WithPrompt("What's the weather in %s?",
location.Location),
+ )
+ if err != nil {
+ return "", err
+ }
+ return resp.Text(), nil
+ })
+}
+
+func TestTextGeneration(t *testing.T) {
+ ctx := context.Background()
+ g, err := manager.GetGlobalGenkit()
+ if err != nil {
+ t.Fatalf("failed to initialize genkit: %v", err)
+ }
+
+ resp, err := genkit.GenerateText(ctx, g, ai.WithPrompt("Hello, Who are
you?"))
+ if err != nil {
+ t.Fatalf("failed to generate text: %v", err)
+ }
+ t.Logf("Generated text: %s", resp)
+
+ fmt.Printf("%s", resp)
+}
+
+func TestWeatherFlowRun(t *testing.T) {
+ ctx := context.Background()
+ g, err := manager.GetGlobalGenkit()
+ if err != nil {
+ t.Fatalf("failed to initialize genkit: %v", err)
+ }
+
+ flow := defineWeatherFlow(g)
+ flow.Run(ctx, WeatherInput{Location: "San Francisco"})
+}
diff --git a/ai/test/test.go b/ai/test/test.go
new file mode 100644
index 00000000..2e302f44
--- /dev/null
+++ b/ai/test/test.go
@@ -0,0 +1,12 @@
+package test
+
+import (
+ "dubbo-admin-ai/internal/agent"
+ "dubbo-admin-ai/plugins/dashscope"
+)
+
+func init() {
+ if err := agent.InitAgent(dashscope.Qwen3); err != nil {
+ panic(err)
+ }
+}
diff --git a/ai/utils/utils.go b/ai/utils/utils.go
new file mode 100644
index 00000000..03ecc837
--- /dev/null
+++ b/ai/utils/utils.go
@@ -0,0 +1,52 @@
+package utils
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+)
+
+// CopyFile 复制源文件内容到目标文件,如果目标文件不存在,则会创建该文件
+// srcPath: 源文件路径
+// dstPath: 目标文件路径
+func CopyFile(srcPath, dstPath string) error {
+ // 打开源文件
+ srcFile, err := os.Open(srcPath)
+ if err != nil {
+ return fmt.Errorf("failed to open source file %s: %w", srcPath,
err)
+ }
+ defer srcFile.Close()
+
+ // 获取源文件信息
+ srcInfo, err := srcFile.Stat()
+ if err != nil {
+ return fmt.Errorf("failed to get source file info %s: %w",
srcPath, err)
+ }
+
+ // 确保目标目录存在
+ dstDir := filepath.Dir(dstPath)
+ if err := os.MkdirAll(dstDir, 0755); err != nil {
+ return fmt.Errorf("failed to create target directory %s: %w",
dstDir, err)
+ }
+
+ // 创建或覆盖目标文件
+ dstFile, err := os.Create(dstPath)
+ if err != nil {
+ return fmt.Errorf("failed to create target file %s: %w",
dstPath, err)
+ }
+ defer dstFile.Close()
+
+ // 复制文件内容
+ _, err = io.Copy(dstFile, srcFile)
+ if err != nil {
+ return fmt.Errorf("failed to copy file content: %w", err)
+ }
+
+ // 设置目标文件权限与源文件相同
+ if err := os.Chmod(dstPath, srcInfo.Mode()); err != nil {
+ return fmt.Errorf("failed to set file permissions: %w", err)
+ }
+
+ return nil
+}