更新 README

This commit is contained in:
ken 2025-03-30 11:15:38 +08:00
parent 6bd0544f45
commit 456498d5ce
10 changed files with 67 additions and 97 deletions

View File

@ -1,15 +1,27 @@
#### 1. AI agent #### 1. AI agent
Agent模块负责处理request工作流生成prompt与LLM交互。 Agent模块负责处理request工作流生成prompt与LLM交互。
> Agent代码:
> <http://ishangsf.com:8100/ken/agent>
>
> websocket接口地址:
> <ws://112.74.39.99:8080/ws>
>
> 接口文档:
> <http://ishangsf.com:8100/ken/agent/src/branch/main/README.md>
>
> 前端接口测试案例代码:
> <http://ishangsf.com:8100/ken/agent/src/branch/main/examples>
>
> 测试api_key: “***simpletest2025***_xxxx” xxxx为任意字符串
Agent 有三模式运行由plugin在request的model参数决定运行模式在交互过程对agent是透明的。 Agent 有三模式运行由plugin在request的model参数决定运行模式在交互过程对agent是透明的。
> 1. agent local model就是agent调用本地部署的LLM模型 > 1. agent local model就是agent调用本地部署的LLM模型
> 2. agent remote api就是agent调用openai或deepseek等外部服务的api > 2. agent remote api就是agent调用openai或deepseek等外部服务的api
> 3. plugin remote api由plugin调用openai或deepseek等外部服务的api > 3. plugin remote api由plugin调用openai或deepseek等外部服务的api
agent网页版测试 <https://host:port/simpletest2025>
前端测试代码 repo/examples
测试api_key: “***simpletest2025***_xxxx” xxxx为任意字符串
##### 1.1 系统框架 ##### 1.1 系统框架

View File

@ -1,7 +1,8 @@
import ws from 'ws'; import ws from 'ws';
process.env["NODE_TLS_REJECT_UNAUTHORIZED"]="0" process.env["NODE_TLS_REJECT_UNAUTHORIZED"]="0"
const ADDR = "wss://127.0.0.1:8080/ws" const ADDR = "ws://112.74.39.99:8080/ws"
// const ADDR = "ws://127.0.0.1:8080/ws"
export function makeClient( export function makeClient(
onMessage: ((data: ws.RawData) => void) | any = undefined, onMessage: ((data: ws.RawData) => void) | any = undefined,

View File

@ -9,7 +9,7 @@ async function run() {
"request_id" : 123, // 随机生成 (必填) "request_id" : 123, // 随机生成 (必填)
"language" : "en", // 语言, en-英文, cn-中文,默认为 en (可选) "language" : "en", // 语言, en-英文, cn-中文,默认为 en (可选)
"msg" : "What would be a good company name a company that makes colorful socks?", "msg" : "What would be a good company name a company that makes colorful socks?",
"model" : "local deepseek-r1:32b", //(必填) "model" : "local qwen2.5-coder:7b", //(必填)
"stream" : false // stream方式返回目前未支持 (可选) "stream" : false // stream方式返回目前未支持 (可选)
} }
@ -21,8 +21,7 @@ async function run_stream() {
let client = await makeClient((data: ws.RawData)=>{ let client = await makeClient((data: ws.RawData)=>{
let d = JSON.parse(data.toString()) let d = JSON.parse(data.toString())
process.stdout.write(d.msg) process.stdout.write(d.msg)
console.log(data.toString()) // console.log(data.toString())
return
}) })
let req = { let req = {
@ -30,7 +29,7 @@ async function run_stream() {
"request_id" : 123, // 随机生成 (必填) "request_id" : 123, // 随机生成 (必填)
"language" : "en", // 语言, en-英文, cn-中文,默认为 en (可选) "language" : "en", // 语言, en-英文, cn-中文,默认为 en (可选)
"msg" : "What would be a good company name a company that makes colorful socks?", "msg" : "What would be a good company name a company that makes colorful socks?",
"model" : "local deepseek-r1:32b", //(必填) "model" : "local qwen2.5-coder:7b", //(必填)
"stream" : true // stream方式返回目前未支持 (可选) "stream" : true // stream方式返回目前未支持 (可选)
} }

View File

@ -16,15 +16,13 @@ type Agent struct {
ctx context.Context ctx context.Context
apicheck *keyChecker apicheck *keyChecker
clients *common.ThreadSafeMap[*client, struct{}] clients *common.ThreadSafeMap[*client, struct{}]
docPath string
} }
var once sync.Once var once sync.Once
func NewAgent(ctx context.Context, docPath string) *Agent { func NewAgent(ctx context.Context) *Agent {
return &Agent{ return &Agent{
ctx : ctx, ctx : ctx,
clients: new(common.ThreadSafeMap[*client, struct{}]).Init(nil, false), clients: new(common.ThreadSafeMap[*client, struct{}]).Init(nil, false),
docPath: docPath,
} }
} }
@ -34,11 +32,10 @@ func (a *Agent) Start(port uint64, crtDir *string) {
go func(){ go func(){
sm := http.NewServeMux() sm := http.NewServeMux()
// sm.HandleFunc("/simpletest2025", a.serveTestPage) // sm.HandleFunc("/simpletest2025", a.serveTestPage)
// sm.HandleFunc("/doc", a.serveDoc)
// sm.HandleFunc("/assets/", a.serveAssets)
sm.HandleFunc("/ws", a.serveWs) sm.HandleFunc("/ws", a.serveWs)
sm.HandleFunc("/doc", a.serveDoc)
sm.HandleFunc("/assets/", a.serveAssets)
addr := fmt.Sprintf(":%d", port) addr := fmt.Sprintf(":%d", port)
log.Info("[agent] start websocket server", "addr", addr) log.Info("[agent] start websocket server", "addr", addr)
@ -70,7 +67,7 @@ func (a *Agent) serveDoc(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return return
} }
http.ServeFile(w, r, a.docPath+"/agent服务协议.html") http.ServeFile(w, r, "./assets/agent服务协议.html")
} }
func (a *Agent) serveAssets(w http.ResponseWriter, r *http.Request) { func (a *Agent) serveAssets(w http.ResponseWriter, r *http.Request) {
@ -80,8 +77,7 @@ func (a *Agent) serveAssets(w http.ResponseWriter, r *http.Request) {
return return
} }
fmt.Println(r.URL.Path[1:]) fmt.Println(r.URL.Path[1:])
http.ServeFile(w, r, a.docPath+"/" +r.URL.Path[1:]) http.ServeFile(w, r, "./"+r.URL.Path[1:])
} }
func (a *Agent) serveWs(w http.ResponseWriter, r *http.Request) { func (a *Agent) serveWs(w http.ResponseWriter, r *http.Request) {

View File

@ -1,36 +1,15 @@
package agent package agent
import ( import (
"agent/src/prompt"
"context" "context"
"fmt"
"strings"
"testing" "testing"
) )
func TestAgent(t *testing.T) { func TestAgent(t *testing.T) {
ctx := context.Background() ctx := context.Background()
a := NewAgent(ctx, "../../docs") a := NewAgent(ctx)
crt := "../../config" // crt := "../../config"
a.Start(8080, &crt) a.Start(8080, nil)
<-make(chan bool) <-make(chan bool)
} }
func Test001(t *testing.T) {
s := "\u003cthink\u003e\nOkay, so I'm trying to understand this code that someone provided. It's about creating a snake game using Python and Tkinter. The selected portion is the __init__ method of the Food class. Let me break it down step by step.\n\nFirst, I see that the Food class has an __init__ method. That means when a new instance of Food is created, this method runs automatically. Inside this method, there are some variables being set: x and y. They're using random.randint to generate these values. \n\nLooking at how x is calculated: it's random.randint(0, (WIDTH / SPACE_SIZE) - 1) multiplied by SPACE_SIZE. Similarly for y. I think WIDTH and HEIGHT are the dimensions of the game window, and SPACE_SIZE is probably the size of each grid block or the size of the food item. So, dividing WIDTH by SPACE_SIZE gives the number of possible positions along the width. Subtracting 1 ensures that the random number doesn't go out of bounds when multiplied back by SPACE_SIZE.\n\nSo x and y are being set to a position within the game window, in increments of SPACE_SIZE. That makes sense because it keeps the food aligned with the grid where the snake moves.\n\nNext, self.coordinates is assigned as a list [x, y]. This probably holds the current position of the food so that other parts of the code can check if the snake has eaten it.\n\nThen there's a call to canvas.create_oval. The parameters are x, y for the top-left corner and x + SPACE_SIZE, y + SPACE_SIZE for the bottom-right corner. The fill color is FOOD, which I assume is defined earlier as a constant. The tag \"food\" might be used later to identify this object on the canvas.\n\nPutting it all together, when a Food object is created, it randomly places an oval-shaped food item somewhere on the game canvas. This position is stored so that the snake can check for collisions or eating events in subsequent turns.\n\nI'm a bit confused about why they use create_oval instead of another shape, but I guess it's just a design choice. Also, I wonder how the food respawns when eaten. Maybe there's another part of the code that deletes the old food and creates a new instance of Food, which would call this __init__ method again.\n\nAnother thing to note is that the coordinates are stored as [x, y], which will be used in collision detection with the snake's head. If the snake's head position matches the food's coordinates, then the snake grows, and the score increases.\n\nI think I've got a good grasp of what this code does now. It initializes the food at a random valid position on the canvas and sets up its visual representation.\n\u003c/think\u003e\n\nThe selected code is from the `__init__` method of the `Food` class in a Python-based snake game. Here's an explanation of its logic:\n\n1. **Random Position Calculation**:\n - The `x` and `y` coordinates for the food are determined using `random.randint()`.\n - `(WIDTH / SPACE_SIZE) - 1` calculates the maximum index along the width, ensuring the food stays within bounds.\n - Multiplying by `SPACE_SIZE` converts this index to pixel coordinates.\n\n2. **Storing Coordinates**:\n - The food's position is stored in `self.coordinates` as `[x, y]`, allowing other parts of the code to check for collisions with the snake.\n\n3. **Visual Representation**:\n - An oval is drawn on the canvas using `create_oval()`, positioned at `(x, y)` with dimensions based on `SPACE_SIZE`.\n - The color is set to `FOOD` (defined earlier), and tagged as \"food\" for easy reference.\n\nThis method initializes the food's position and appearance each time a new `Food` object is created."
fmt.Println(prompt.RemoveThinkPart(s))
}
func Test002(t *testing.T) {
prompt_docstring := strings.Replace(
`a
file: {%s}
"""
{%s}
"""
`,
"\"\"\"", "```", 2)
fmt.Println(prompt_docstring)
// fmt.Println(fmt.Sprintf(prompt_docstring, "a", "b"))
}

View File

@ -1,7 +1,6 @@
package llm package llm
import ( import (
"agent/src/utils"
"context" "context"
"github.com/tmc/langchaingo/llms" "github.com/tmc/langchaingo/llms"
@ -11,33 +10,29 @@ import (
var LLMGroups map[string]*LLMGroup var LLMGroups map[string]*LLMGroup
var LLMNames []string var LLMNames []string
// const ollamaurl = "http://192.168.1.5:11434"
const ollamaurl = "http://ishangsf.com:11434"
func init() { func init() {
LLMGroups = make(map[string]*LLMGroup) models := []string{"qwen2.5-coder:7b", "deepseek-coder-v2:16b"}
llm, err := ollama.New(ollama.WithServerURL("http://192.168.1.5:11434"), ollama.WithModel("deepseek-r1:32b")) LLMNames = make([]string, len(models))
if err != nil { LLMGroups = make(map[string]*LLMGroup, len(models))
panic(err)
}
g := &LLMGroup{
name: "local deepseek-r1:32b",
llm: make(chan llms.Model, 1),
local: true,
}
g.llm <- llm
LLMGroups["local deepseek-r1:32b"] = g
llm, err = ollama.New(ollama.WithServerURL("http://192.168.1.5:11434"), ollama.WithModel("deepseek-coder-v2:16b")) for i, model := range models {
if err != nil { llm, err := ollama.New(ollama.WithServerURL(ollamaurl), ollama.WithModel(model))
panic(err) if err != nil {
panic(err)
}
model = "local " + model
g := &LLMGroup{
name: model,
llm: make(chan llms.Model, 1),
local: true,
}
g.llm <- llm
LLMGroups[model] = g
LLMNames[i] = model
} }
g = &LLMGroup{
name: "local deepseek-coder-v2:16b",
llm: make(chan llms.Model, 1),
local: true,
}
g.llm <- llm
LLMGroups["local deepseek-coder-v2:16b"] = g
LLMNames = utils.MapKeys(LLMGroups)
} }

View File

@ -1,9 +1,8 @@
package llm package llm
import ( import (
"agent/src/utils"
"context" "context"
"sync" "fmt"
"testing" "testing"
"github.com/tmc/langchaingo/llms" "github.com/tmc/langchaingo/llms"
@ -12,33 +11,19 @@ import (
func TestOllama(t *testing.T) { func TestOllama(t *testing.T) {
// llm, err := ollama.New(ollama.WithServerURL("http://192.168.1.5:11434"), ollama.WithModel("deepseek-r1:32b")) // llm, err := ollama.New(ollama.WithServerURL("http://192.168.1.5:11434"), ollama.WithModel("deepseek-r1:32b"))
llm, err := ollama.New(ollama.WithServerURL("http://ishangsf.com:11434"), ollama.WithModel("deepseek-r1:32b")) llm, err := ollama.New(ollama.WithServerURL("http://ishangsf.com:11434"), ollama.WithModel("qwen2.5-coder:7b"))
if err != nil { if err != nil {
panic(err) panic(err)
} }
ctx := context.Background() ctx := context.Background()
var wg sync.WaitGroup content := []llms.MessageContent{
wg.Add(2) llms.TextParts(llms.ChatMessageTypeSystem, "You are a company branding design wizard."),
for range 1 { llms.TextParts(llms.ChatMessageTypeHuman, "What would be a good company name a company that makes colorful socks?"),
go func() {
content := []llms.MessageContent{
llms.TextParts(llms.ChatMessageTypeSystem, "You are a company branding design wizard."),
llms.TextParts(llms.ChatMessageTypeHuman, "What would be a good company name a company that makes colorful socks?"),
}
// completion, err := llm.GenerateContent(ctx, content, llms.WithStreamingFunc(func(ctx context.Context, chunk []byte) error {
// fmt.Print(string(chunk))
// return nil
// }))
completion, err := llm.GenerateContent(ctx, content)
if err != nil {
panic(err)
}
_ = completion
utils.PrintJson(completion.Choices)
wg.Done()
}()
} }
wg.Wait() llm.GenerateContent(ctx, content, llms.WithStreamingFunc(func(ctx context.Context, chunk []byte) error {
fmt.Print(string(chunk))
return nil
}))
} }

View File

@ -18,7 +18,7 @@ var (
main_data = struct { main_data = struct {
port uint64 port uint64
crtDir string crtDir string
docs string serveDocs string
saveRequestTo string saveRequestTo string
}{} }{}
@ -29,7 +29,6 @@ func init() {
f := cmd_run.Flags() f := cmd_run.Flags()
f.Uint64Var(&main_data.port, "port", 8080, "http port") f.Uint64Var(&main_data.port, "port", 8080, "http port")
f.StringVar(&main_data.crtDir, "crt", "", "where is server.crt, server.key (default no https)") f.StringVar(&main_data.crtDir, "crt", "", "where is server.crt, server.key (default no https)")
f.StringVar(&main_data.docs, "docs", "./docs", "where is the document directory")
// TODO // TODO
// f.StringVar(&main_data.saveRequestTo, "save_request_to", "", "save request to dir (default not save)") // f.StringVar(&main_data.saveRequestTo, "save_request_to", "", "save request to dir (default not save)")
@ -53,7 +52,7 @@ var cmd_run = &cobra.Command{
crtDir = &main_data.crtDir crtDir = &main_data.crtDir
} }
fmt.Println(main_data.saveRequestTo) fmt.Println(main_data.saveRequestTo)
a := agent.NewAgent(cmd.Context(), main_data.docs) a := agent.NewAgent(cmd.Context())
a.Start(main_data.port, crtDir) a.Start(main_data.port, crtDir)
_ = saveRequestTo _ = saveRequestTo

View File

@ -41,7 +41,7 @@ type RequestExec struct {
RequestId uint64 `json:"request_id"` RequestId uint64 `json:"request_id"`
Language string `json:"language,omitempty"` Language string `json:"language,omitempty"`
Model string `json:"model"` Model string `json:"model"`
Stream bool `json:"stream"` Stream bool `json:"stream,omitempty"`
} }
func (m *RequestExec) Check() error { func (m *RequestExec) Check() error {

View File

@ -85,16 +85,20 @@ func Handle(client common.WsClient, msg []byte) error {
var execReq *message.RequestExec var execReq *message.RequestExec
if strings.HasPrefix(cmd.Cmd, "exec_") { if strings.HasPrefix(cmd.Cmd, "exec_") {
execReq, err = message.Parse[message.RequestExec](msg) execReq, err = message.Parse[message.RequestExec](msg)
if err != nil || execReq.Check() != nil { if err != nil {
errMsg = "invalid request format" errMsg = "invalid request format"
return err return err
} }
if err = execReq.Check(); err != nil {
errMsg = err.Error()
return err
}
} }
if cmd.Cmd == "ide_service" { if cmd.Cmd == "ide_service" {
req, err := message.Parse[message.MsgIDEService](msg) req, err := message.Parse[message.MsgIDEService](msg)
if err != nil { if err != nil {
errMsg = "invalid request format" errMsg = "invalid ide request format"
return err return err
} }
err = client.NewWantResponse(req.RequestId, msg) err = client.NewWantResponse(req.RequestId, msg)
@ -134,7 +138,7 @@ func Handle(client common.WsClient, msg []byte) error {
task.request = req task.request = req
task.handler = task.exec_chat task.handler = task.exec_chat
default: default:
errMsg = "invalid request format" errMsg = "unknown cmd"
return err return err
} }