Skip to content

Commit daa056f

Browse files
committed
feat: code-analysis agent
1 parent 5e7205f commit daa056f

10 files changed

Lines changed: 259 additions & 46 deletions

File tree

README.md

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ see [UniAST Specification](docs/uniast-zh.md)
2626

2727
# Quick Start
2828

29-
Below is a quick start guide for using ABCoder to build a coding context on both internal and external libraies.
29+
## Use ABCoder as a MCP server
3030

3131
1. Install ABCoder:
3232

@@ -56,7 +56,7 @@ Below is a quick start guide for using ABCoder to build a coding context on both
5656
"command": "abcoder",
5757
"args": [
5858
"mcp",
59-
"{the-AST-directory}" // EX: "/abcoder-asts"
59+
"{the-AST-directory}"
6060
]
6161
}
6262
}
@@ -66,11 +66,41 @@ Below is a quick start guide for using ABCoder to build a coding context on both
6666
6767
4. Enjoy it!
6868
69-
See [using ABCoder in TRAE](https://bytedance.sg.larkoffice.com/file/SEmdbLpC1oCbclxmc5Dlkp9fg7r). Tips:
70-
71-
- You can add more repo ASTs into the AST directory without restarting abcoder MCP server.
69+
See [using ABCoder MCP in TRAE demo](https://bytedance.sg.larkoffice.com/file/SEmdbLpC1oCbclxmc5Dlkp9fg7r).
70+
71+
## Tips:
7272
73-
- Try to use [the recommaned prompt](llm/prompt/analyzer.md) and combine planning/memory tools like [sequential-thinking](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking) in your AI agent.
73+
- You can add more repo ASTs into the AST directory without restarting abcoder MCP server.
74+
75+
- Try to use [the recommaned prompt](llm/prompt/analyzer.md) and combine planning/memory tools like [sequential-thinking](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking) in your AI agent.
76+
77+
78+
## Use ABCoder as an Agent (WIP)
79+
80+
You can alse use ABCoder as a command-line Agent like:
81+
82+
```bash
83+
API_TYPE='{openai|ollama|ark|claude}' API_KEY='{your-api-key}' MODEL_NAME='{model-endpoint}' abcoder agent {the-AST-directory}
84+
```
85+
For example:
86+
87+
```bash
88+
$ API_TYPE='ark' API_KEY='xxx' MODEL_NAME='zzz' abcoder agent ./testdata/asts
89+
90+
Hello! I'm ABCoder, your coding assistant. What can I do for you today?
91+
92+
$ what the repo 'localsession' does?
93+
94+
The `localsession` repository appears to be a Go module (`github.com/cloudwego/localsession`) that provides functionality related to managing local sessions. Here's a breakdown of its structure and purpose:
95+
...
96+
If you'd like to explore specific functionalities or code details, let me know, and I can dive deeper into the relevant files or nodes. For example:
97+
- What does `session.go` or `manager.go` implement?
98+
- How is the backup functionality used?
99+
100+
$ exit
101+
```
102+
103+
- NOTICE: This feature is Work-In-Progress. It only support code-analyzing at present.
74104

75105

76106
# Supported Languages

llm/agent/analyzer.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
_ "embed"
2222

23+
"github.com/cloudwego/abcoder/lang/log"
2324
"github.com/cloudwego/abcoder/llm"
2425
"github.com/cloudwego/abcoder/llm/prompt"
2526
"github.com/cloudwego/abcoder/llm/tool"
@@ -35,36 +36,31 @@ type RepoAnnalyzerOptions struct {
3536
}
3637

3738
func NewRepoAnalyzer(ctx context.Context, opts RepoAnnalyzerOptions) *llm.ReactAgent {
39+
log.Debug("NewRepoAnalyzer, opts: %+v", opts)
40+
3841
exeModel := llm.NewChatModel(opts.ModelConfig)
3942
ast := tool.NewASTReadTools(tool.ASTReadToolsOptions{
4043
RepoASTsDir: opts.ASTsDir,
4144
})
4245

4346
// AST tools
4447
ts := ast.GetTools()
48+
log.Debug("NewRepoAnalyzer, get AST tools: %#v", ts)
4549
tcfg := compose.ToolsNodeConfig{}
4650
for _, t := range ts {
4751
tcfg.Tools = append(tcfg.Tools, t.(etool.BaseTool))
4852
}
4953

5054
// Sequential thinking tools
5155
tools, err := tool.GetSequentialThinkingTools(ctx)
56+
log.Debug("NewRepoAnalyzer, get sequential-thinking tools: %#v", tools)
5257
if err != nil {
5358
panic(err)
5459
}
5560
for _, t := range tools {
5661
tcfg.Tools = append(tcfg.Tools, t.(etool.BaseTool))
5762
}
5863

59-
// git tools
60-
gits, err := tool.GetGitTools(ctx)
61-
if err != nil {
62-
panic(err)
63-
}
64-
for _, t := range gits {
65-
tcfg.Tools = append(tcfg.Tools, t.(etool.BaseTool))
66-
}
67-
6864
return llm.NewReactAgent("repo-analyzer", llm.ReactAgentOptions{
6965
SysPrompt: prompt.NewTextPrompt(prompt.PromptAnalyzeRepo),
7066
AgentConfig: &react.AgentConfig{

llm/agent/analyzer_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func TestAnalyzer(t *testing.T) {
4040
// APIKey: os.Getenv("CLAUDE_API_KEY"),
4141
// EndPoint: "claude-3-7-sonnet-20250219",
4242
// BaseURL: "https://api.anthropic.com/",
43-
Type: llm.ModelTypeARK,
43+
APIType: llm.ModelTypeARK,
4444
APIKey: os.Getenv("ARK_API_KEY"),
4545
ModelName: os.Getenv("ARK_DEEPSEEK_V3"),
4646
MaxTokens: 1024 * 128,

llm/agent/cmd.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* Copyright 2025 ByteDance Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package agent
18+
19+
import (
20+
"bufio"
21+
"context"
22+
"fmt"
23+
"os"
24+
"strings"
25+
26+
"github.com/cloudwego/abcoder/llm"
27+
"github.com/cloudwego/abcoder/llm/log"
28+
"github.com/cloudwego/eino/compose"
29+
"github.com/cloudwego/eino/flow/agent"
30+
"github.com/cloudwego/eino/schema"
31+
)
32+
33+
type AgentOptions struct {
34+
ASTsDir string
35+
MaxHistories int
36+
MaxSteps int
37+
Model llm.ModelConfig
38+
}
39+
40+
type Agent struct {
41+
opts AgentOptions
42+
analyzer *llm.ReactAgent
43+
histories *Histories
44+
}
45+
46+
// run agent as a repl cmd server
47+
func NewAgent(opts AgentOptions) *Agent {
48+
ag := NewRepoAnalyzer(context.Background(), RepoAnnalyzerOptions{
49+
ASTsDir: opts.ASTsDir,
50+
MaxSteps: opts.MaxSteps,
51+
ModelConfig: opts.Model,
52+
})
53+
54+
histories := NewHistories(opts.MaxHistories)
55+
56+
return &Agent{
57+
opts: opts,
58+
analyzer: ag,
59+
histories: histories,
60+
}
61+
}
62+
63+
func (a *Agent) Generate(ctx context.Context, msgs []*schema.Message) (*schema.Message, error) {
64+
return a.analyzer.Generate(ctx, msgs, agent.WithComposeOptions(compose.WithCallbacks(llm.CallbackHandler{})))
65+
}
66+
67+
func (a *Agent) Run(ctx context.Context) {
68+
fmt.Fprintf(os.Stdout, "Hello! I'm ABCoder, your coding assistant. What can I do for you today?\n")
69+
70+
sc := bufio.NewScanner(os.Stdin)
71+
72+
for sc.Scan() {
73+
74+
query := strings.TrimSpace(sc.Text())
75+
if query == "" {
76+
continue
77+
}
78+
if query == "exit" {
79+
break
80+
}
81+
82+
// get histories
83+
a.histories.Add(&schema.Message{
84+
Role: schema.User,
85+
Content: query,
86+
})
87+
88+
resp, err := a.Generate(ctx, a.histories.Get())
89+
if err != nil {
90+
log.Error("Failed to run agent: %v\n", err)
91+
continue
92+
}
93+
94+
a.histories.Add(resp)
95+
96+
fmt.Fprintf(os.Stdout, "\n%s\n", resp.Content)
97+
}
98+
}
99+
100+
type Histories struct {
101+
max int
102+
hs []*schema.Message
103+
}
104+
105+
func NewHistories(max int) *Histories {
106+
return &Histories{
107+
max: max,
108+
hs: make([]*schema.Message, 0, max),
109+
}
110+
}
111+
112+
func (h *Histories) Add(msg *schema.Message) {
113+
if len(h.hs) >= h.max {
114+
h.hs = h.hs[1:]
115+
}
116+
h.hs = append(h.hs, msg)
117+
}
118+
119+
func (h *Histories) Get() []*schema.Message {
120+
return h.hs
121+
}

llm/api.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package llm
1818

1919
import (
2020
"context"
21+
"strings"
2122

2223
"github.com/cloudwego/abcoder/llm/prompt"
2324
"github.com/cloudwego/abcoder/llm/tool"
@@ -29,7 +30,7 @@ import (
2930

3031
type ModelConfig struct {
3132
Name string `json:"name"` // alias of the config, not endpoint!
32-
Type ModelType `json:"type"`
33+
APIType ModelType `json:"type"`
3334
BaseURL string `json:"base_url"`
3435
APIKey string `json:"api_key"`
3536
ModelName string `json:"model_name"` // the endpoint of the model, like `claude-opus-4-20250514`
@@ -40,11 +41,26 @@ type ModelConfig struct {
4041

4142
type ModelType string
4243

44+
func NewModelType(t string) ModelType {
45+
switch strings.ToLower(t) {
46+
case "ollama":
47+
return ModelTypeOllama
48+
case "ark":
49+
return ModelTypeARK
50+
case "openai":
51+
return ModelTypeOpenAI
52+
case "claude":
53+
return ModelTypeClaude
54+
}
55+
return ModelTypeUnknown
56+
}
57+
4358
const (
44-
ModelTypeOllama ModelType = "ollama"
45-
ModelTypeARK ModelType = "ark"
46-
ModelTypeOpenAI ModelType = "openai" // Fixed typo in constant name
47-
ModelTypeClaude ModelType = "claude"
59+
ModelTypeUnknown ModelType = ""
60+
ModelTypeOllama ModelType = "ollama"
61+
ModelTypeARK ModelType = "ark"
62+
ModelTypeOpenAI ModelType = "openai" // Fixed typo in constant name
63+
ModelTypeClaude ModelType = "claude"
4864
)
4965

5066
type AgentConfig struct {

llm/log/logger.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,19 @@ func NewStdLogger() *StdLogger {
4242
}
4343

4444
func (l *StdLogger) Infof(s string, args ...interface{}) {
45-
l.infologger.Output(2, fmt.Sprintf(s, args...))
45+
l.infologger.Output(3, fmt.Sprintf(s, args...))
4646
}
4747

4848
func (l *StdLogger) Errorf(s string, args ...interface{}) {
49-
l.errlogger.Output(2, fmt.Sprintf(s, args...))
49+
l.errlogger.Output(3, fmt.Sprintf(s, args...))
5050
}
5151

5252
func (l *StdLogger) Debugf(s string, args ...interface{}) {
53-
l.debuglogger.Output(2, fmt.Sprintf(s, args...))
53+
l.debuglogger.Output(3, fmt.Sprintf(s, args...))
5454
}
5555

5656
func (l *StdLogger) Output(calldepth int, s string) error {
57-
return l.infologger.Output(calldepth+1, s)
57+
return l.infologger.Output(calldepth+2, s)
5858
}
5959

6060
type LogLevel uint8

llm/model.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ import (
2626
)
2727

2828
func NewChatModel(m ModelConfig) (model ChatModel) {
29+
if m.MaxTokens == 0 {
30+
m.MaxTokens = 16 * 1024
31+
}
2932
var err error
30-
switch m.Type {
33+
switch m.APIType {
3134
case ModelTypeARK:
3235
model, err = ark.NewChatModel(context.Background(), &ark.ChatModelConfig{
3336
BaseURL: m.BaseURL,
@@ -68,7 +71,7 @@ func NewChatModel(m ModelConfig) (model ChatModel) {
6871
MaxTokens: m.MaxTokens,
6972
})
7073
default:
71-
panic("unsupported model type " + m.Type)
74+
panic("unsupported model type " + m.APIType)
7275
}
7376
return
7477
}

llm/prompt/analyzer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Role
2-
You are a code analysis expert. Based on the Abstract Syntax Tree (AST) of a specific code repository and using relevant tools, you will answer and fulfill user requirements.
2+
You are a code-analysis expert. Based on the Abstract-Syntax-Tree (AST) of a specific code repository and using relevant tools, you will answer user questions.
33

44
# Available Tools
55
- `list_repos`: check the avaliable repos and their correct name

0 commit comments

Comments
 (0)