├── docs └── rag.md ├── pkg ├── README.md ├── .DS_Store ├── strings │ └── strings.go ├── structx │ ├── set.go │ ├── generic.go │ └── map.go ├── browser │ ├── dom │ │ ├── process_dom_test.go │ │ └── utils_test.go │ └── utils.go └── adb │ └── manager.go ├── tool ├── adb │ ├── element.go │ └── registry.go ├── event.go ├── nil.go ├── search │ └── engine │ │ ├── bing.go │ │ └── engine.go ├── crawl │ ├── crawler.go │ ├── tool.go │ ├── jina.go │ └── colly.go ├── datetime │ └── tool.go ├── weather │ └── weather.go ├── config.go ├── file │ ├── create_file.go │ └── write_file.go ├── user │ └── inquiry.go ├── factory.go ├── browser │ ├── dom │ │ ├── process_dom_test.go │ │ └── utils_test.go │ └── controller │ │ └── utils.go ├── entity.go ├── mobile │ └── controller │ │ └── action.go ├── think │ └── think.go └── wikipedia │ └── wikipedia.go ├── example ├── memory │ └── main.go ├── bookkeeper │ ├── web │ │ ├── src │ │ │ ├── vite-env.d.ts │ │ │ ├── main.tsx │ │ │ ├── App.css │ │ │ └── index.css │ │ ├── tsconfig.json │ │ ├── .gitignore │ │ ├── index.html │ │ ├── vite.config.ts │ │ ├── tsconfig.node.json │ │ ├── tsconfig.app.json │ │ ├── eslint.config.js │ │ ├── package.json │ │ ├── public │ │ │ └── vite.svg │ │ └── README.md │ ├── config │ │ ├── config.yaml │ │ └── config.go │ ├── models │ │ ├── user.go │ │ └── transaction.go │ ├── database │ │ └── database.go │ ├── api │ │ ├── chat_handler.go │ │ └── analysis.go │ ├── readme │ └── services │ │ ├── chat_service.go │ │ └── image_service.go ├── deepdoc │ ├── 企业级 SaaS 行业增长白皮书.pdf │ └── main.go ├── mcp │ └── mcp_config │ │ └── servers.json ├── llm │ ├── moonshot │ │ └── moonshot.go │ ├── ollama │ │ └── ollama.go │ ├── zhipu │ │ └── zhipu.go │ ├── vision │ │ └── main.go │ ├── doubao │ │ └── doubao.go │ ├── claude │ │ └── claude.go │ ├── deepseek │ │ └── deepseek.go │ ├── asure-openai │ │ └── openai.go │ ├── openai-c │ │ └── openai-c.go │ └── qwen │ │ └── qwen.go ├── agent │ ├── browser │ │ └── main.go │ ├── memory │ │ └── agent.go │ └── finance │ │ └── finance.go ├── rag │ ├── pgvector │ │ └── main.go │ ├── main.go │ └── milvus │ │ └── main.go └── storage │ └── main.go ├── program ├── completion.go ├── action.go ├── cot.go ├── option.go ├── program.go ├── prompt.go └── response.go ├── speech ├── tts │ ├── minmax │ │ └── basic.go │ ├── tts.go │ ├── cache.go │ ├── tencent │ │ ├── stream.go │ │ └── options.go │ └── aliyun │ │ ├── utils.go │ │ ├── handler.go │ │ └── client.go ├── outbound.go ├── result.go ├── asr.go ├── outbound │ ├── outbound.go │ ├── file.go │ ├── ratelimit.go │ └── websocket.go ├── entities.go ├── asr │ ├── tencent │ │ ├── option.go │ │ ├── listener.go │ │ └── tencent.go │ └── mock.go ├── tts.go └── encoding.go ├── embedding ├── hunyuan_embedder.go ├── embedding.go ├── string_embedder_test.go ├── string_embedder.go ├── openai_embedder.go └── onnx_embedder.go ├── rag ├── deepdoc │ ├── .DS_Store │ ├── document.go │ ├── mdx.go │ ├── extractor │ │ ├── unknown.go │ │ ├── extractor.go │ │ └── document.go │ ├── txtx.go │ ├── json.go │ ├── csv.go │ ├── docx.go │ ├── pptx.go │ ├── xlsx.go │ ├── chunk │ │ └── chunk_test.go │ ├── doc.go │ └── pdf.go ├── retirval.go ├── rag.go └── indexer.go ├── workflow ├── engine.go ├── edge.go ├── node │ ├── vdb.go │ ├── start.go │ ├── expr.go │ ├── node.go │ ├── factory.go │ ├── tool.go │ ├── llm.go │ └── end.go ├── workflow.go ├── definition.go └── node.go ├── vdb ├── distance.go ├── option.go └── vdb.go ├── agent ├── contexts.go ├── response.go └── memory.go ├── prompt ├── templates │ ├── simple.json │ ├── intent.go │ ├── meta.go │ └── example.go ├── prompt.go ├── readme ├── format.go └── simple_format.go ├── llm ├── cache_ranker.go ├── logger.go ├── cache_metrics.go ├── qwen │ └── llm.go ├── config.go ├── openai-c │ └── llm.go ├── doubao │ └── doubao.go ├── anthropic │ └── claude.go ├── deepseek │ └── llm.go ├── cache_query.go ├── cache.go ├── mock.go ├── ollama │ └── llm.go ├── cache_redis.go └── hook.go ├── memory ├── memory.go └── memory_item.go ├── vision ├── options.go ├── siliconflow │ └── vision.go ├── hook.go └── vision.go ├── .gitignore ├── mcp_config └── servers.json ├── storage ├── session.go ├── storage.go ├── postgres_store.go └── json_store.go └── LICENSE /docs/rag.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/README.md: -------------------------------------------------------------------------------- 1 | # utils for struct -------------------------------------------------------------------------------- /tool/adb/element.go: -------------------------------------------------------------------------------- 1 | package adb 2 | -------------------------------------------------------------------------------- /example/memory/main.go: -------------------------------------------------------------------------------- 1 | package memory 2 | -------------------------------------------------------------------------------- /program/completion.go: -------------------------------------------------------------------------------- 1 | package program 2 | -------------------------------------------------------------------------------- /speech/tts/minmax/basic.go: -------------------------------------------------------------------------------- 1 | package minmax 2 | -------------------------------------------------------------------------------- /embedding/hunyuan_embedder.go: -------------------------------------------------------------------------------- 1 | package embedding 2 | -------------------------------------------------------------------------------- /pkg/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/showntop/llmack/HEAD/pkg/.DS_Store -------------------------------------------------------------------------------- /example/bookkeeper/web/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /rag/deepdoc/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/showntop/llmack/HEAD/rag/deepdoc/.DS_Store -------------------------------------------------------------------------------- /example/deepdoc/企业级 SaaS 行业增长白皮书.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/showntop/llmack/HEAD/example/deepdoc/企业级 SaaS 行业增长白皮书.pdf -------------------------------------------------------------------------------- /tool/event.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | // Event 事件 4 | type Event struct { 5 | Name string 6 | Type string 7 | Data any 8 | } 9 | -------------------------------------------------------------------------------- /rag/deepdoc/document.go: -------------------------------------------------------------------------------- 1 | package deepdoc 2 | 3 | // Document ... 4 | type Document struct { 5 | ID string 6 | Name string 7 | } 8 | -------------------------------------------------------------------------------- /program/action.go: -------------------------------------------------------------------------------- 1 | package program 2 | 3 | type Action struct { 4 | Name string 5 | Description string 6 | Parameters map[string]any 7 | } 8 | -------------------------------------------------------------------------------- /workflow/engine.go: -------------------------------------------------------------------------------- 1 | package workflow 2 | 3 | // Executor ... 4 | type Executor interface { 5 | // TODO 6 | } 7 | 8 | func xxx() { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /speech/outbound.go: -------------------------------------------------------------------------------- 1 | package speech 2 | 3 | // Outbound ... 4 | type Outbound interface { 5 | Write([]byte) error 6 | Reset() error 7 | Close() error 8 | } 9 | -------------------------------------------------------------------------------- /vdb/distance.go: -------------------------------------------------------------------------------- 1 | package vdb 2 | 3 | type Distance string 4 | 5 | const ( 6 | DistanceL2 Distance = "l2" 7 | DistanceCosine Distance = "cosine" 8 | ) 9 | -------------------------------------------------------------------------------- /example/bookkeeper/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /example/bookkeeper/config/config.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | database: 5 | host: localhost 6 | port: 5432 7 | user: postgres_admin 8 | password: xxxx 9 | dbname: bookkeeper 10 | -------------------------------------------------------------------------------- /embedding/embedding.go: -------------------------------------------------------------------------------- 1 | package embedding 2 | 3 | import "context" 4 | 5 | // Embedder ... 6 | type Embedder interface { 7 | Embed(ctx context.Context, text string) ([]float32, error) 8 | Dimension() int 9 | } 10 | -------------------------------------------------------------------------------- /speech/result.go: -------------------------------------------------------------------------------- 1 | package speech 2 | 3 | import "github.com/showntop/llmack/llm" 4 | 5 | // AudioChunk ... 6 | type AudioChunk struct { 7 | Audio string 8 | Text string 9 | ToolCalls []llm.ToolCall 10 | } 11 | -------------------------------------------------------------------------------- /speech/asr.go: -------------------------------------------------------------------------------- 1 | package speech 2 | 3 | // ASR ... 4 | type ASR interface { 5 | Recognize(buffer []byte) (string, error) 6 | Input(buffer []byte) error // 流入音频数据, async recognize 7 | Close() error // 流入音频数据, async recognize 8 | } 9 | -------------------------------------------------------------------------------- /agent/contexts.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import "time" 4 | 5 | type Memory struct { 6 | ID string `json:"id"` 7 | Content string `json:"content"` 8 | CreatedAt time.Time `json:"created_at"` 9 | UpdatedAt time.Time `json:"updated_at"` 10 | } 11 | -------------------------------------------------------------------------------- /prompt/templates/simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "context_prompt": "用户在与一个客观的助手对话。助手会尊重找到的材料,给出全面专业的解释,但不会过度演绎。同时回答中不会暴露引用的材料:\n\n```\n{{#context#}}\n```\n", 3 | "system_prompt_orders": [ 4 | "context_prompt", 5 | "pre_prompt" 6 | ], 7 | "query_prompt": "{{#query#}}", 8 | "stops": null 9 | } -------------------------------------------------------------------------------- /example/bookkeeper/web/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.tsx' 5 | 6 | createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /llm/cache_ranker.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | // Scorer 打分 4 | type Scorer interface { 5 | Eval(string) float64 6 | } 7 | 8 | // ExactMatchScorer 精确匹配排序 9 | type ExactMatchScorer struct { 10 | } 11 | 12 | // Eval 精确匹配排序 13 | func (r *ExactMatchScorer) Eval(query string) float64 { 14 | return 1.0 15 | } 16 | -------------------------------------------------------------------------------- /pkg/strings/strings.go: -------------------------------------------------------------------------------- 1 | package strings 2 | 3 | import "strings" 4 | 5 | // TrimSpecial trim tab \n \r \t 等特殊字符 6 | func TrimSpecial(s string) string { 7 | s = strings.ReplaceAll(s, "\n", " ") 8 | s = strings.ReplaceAll(s, "\r", " ") 9 | s = strings.ReplaceAll(s, "\t", " ") 10 | return s 11 | } 12 | -------------------------------------------------------------------------------- /tool/nil.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | var NilTool = &Tool{ 9 | Name: "nil", 10 | Description: "nil tool", 11 | Kind: "code", 12 | invoke: func(ctx context.Context, args string) (string, error) { 13 | return "", fmt.Errorf("not implement") 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /memory/memory.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // Extra ... 8 | type Extra any 9 | 10 | type Memory interface { 11 | Get(context.Context, string) ([]*MemoryItem, error) 12 | Add(context.Context, string, *MemoryItem) error 13 | FetchHistories(context.Context, string) ([]*MemoryItem, error) 14 | } 15 | -------------------------------------------------------------------------------- /rag/deepdoc/mdx.go: -------------------------------------------------------------------------------- 1 | package deepdoc 2 | 3 | // MdxDocument ... 4 | type MdxDocument struct { 5 | } 6 | 7 | // Mdx ... 8 | func Mdx() *MdxDocument { 9 | return &MdxDocument{} 10 | } 11 | 12 | // Extract ... 13 | func (d *MdxDocument) Extract(filename string, binary []byte) ([]string, error) { 14 | return []string{string(binary)}, nil 15 | } 16 | -------------------------------------------------------------------------------- /example/bookkeeper/web/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /vision/options.go: -------------------------------------------------------------------------------- 1 | package vision 2 | 3 | // InvokeOption is a function that configures a InvokeOptions. 4 | type InvokeOption func(*InvokeOptions) 5 | 6 | // InvokeOptions ... 7 | type InvokeOptions struct { 8 | ApiKey string 9 | } 10 | 11 | // WithApiKey ... 12 | func WithApiKey(apiKey string) InvokeOption { 13 | return func(o *InvokeOptions) { 14 | o.ApiKey = apiKey 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /rag/deepdoc/extractor/unknown.go: -------------------------------------------------------------------------------- 1 | package extractor 2 | 3 | // NewUnknownExtractor ... 4 | func NewUnknownExtractor() *UnknownExtractor { 5 | 6 | return &UnknownExtractor{} 7 | } 8 | 9 | // UnknownExtractor ... 10 | type UnknownExtractor struct { 11 | } 12 | 13 | // Extract ... 14 | func (d *UnknownExtractor) Extract(m *Meta, binary []byte) ([]string, error) { 15 | return nil, nil 16 | } 17 | -------------------------------------------------------------------------------- /workflow/edge.go: -------------------------------------------------------------------------------- 1 | package workflow 2 | 3 | // Edge TODO 4 | type Edge struct { 5 | ID string `json:"id"` // 线段id 6 | Name string `json:"name,omitempty"` 7 | Source string `json:"source"` 8 | Target string `json:"target"` 9 | ExpressName string `json:"expr_name,omitempty"` 10 | Express string `json:"expr_content,omitempty"` 11 | // Metadata meta `json:"metadata"` 12 | } 13 | -------------------------------------------------------------------------------- /tool/search/engine/bing.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // NewBing bing search 8 | func NewBing(apiKey string) Searcher { 9 | return &Serper{apiKey: apiKey} 10 | } 11 | 12 | // Bing search by big 13 | type Bing struct { 14 | apiKey string 15 | } 16 | 17 | // Search 使用serper搜索 18 | func (s *Bing) Search(ctx context.Context, query string) ([]*Result, error) { 19 | return nil, nil 20 | } 21 | -------------------------------------------------------------------------------- /rag/deepdoc/txtx.go: -------------------------------------------------------------------------------- 1 | package deepdoc 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // TxtxDocument ... 8 | type TxtxDocument struct { 9 | } 10 | 11 | // Txtx ... 12 | func Txtx() *TxtxDocument { 13 | return &TxtxDocument{} 14 | } 15 | 16 | // Extract ... 17 | func (d *TxtxDocument) Extract(filename string, binary []byte) ([]string, error) { 18 | sections := strings.Split(string(binary), "\n") 19 | return sections, nil 20 | } 21 | -------------------------------------------------------------------------------- /tool/crawl/crawler.go: -------------------------------------------------------------------------------- 1 | package crawl 2 | 3 | import "context" 4 | 5 | // Result 爬取结果 6 | type Result struct { 7 | Link string 8 | Title string 9 | Content string 10 | } 11 | 12 | // Crawler 爬虫接口 13 | type Crawler interface { 14 | Crawl(ctx context.Context, url string) (*Result, error) 15 | } 16 | 17 | var Crawlers = make(map[string]Crawler) 18 | 19 | func Register(name string, c Crawler) { 20 | Crawlers[name] = c 21 | } 22 | -------------------------------------------------------------------------------- /example/bookkeeper/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/bookkeeper/models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type User struct { 10 | gorm.Model 11 | Username string `gorm:"uniqueIndex;not null" json:"username"` 12 | Password string `gorm:"not null" json:"-"` 13 | Email string `gorm:"uniqueIndex;not null" json:"email"` 14 | LastLoginAt time.Time `json:"last_login_at"` 15 | IsActive bool `gorm:"default:true" json:"is_active"` 16 | } 17 | -------------------------------------------------------------------------------- /memory/memory_item.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import "time" 4 | 5 | type MemoryItem struct { 6 | ID int64 7 | SessionID string 8 | Content string 9 | Topics []string 10 | Extra *Extra 11 | CreatedAt time.Time 12 | UpdatedAt time.Time 13 | } 14 | 15 | func NewMemoryItem(sessionID string, content string, extra *Extra) *MemoryItem { 16 | return &MemoryItem{ 17 | SessionID: sessionID, 18 | Content: content, 19 | Extra: extra, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /speech/tts/tts.go: -------------------------------------------------------------------------------- 1 | package tts 2 | 3 | import "context" 4 | 5 | // TTS is the struct for text to speech 6 | type TTS struct { 7 | } 8 | 9 | // newTTS creates a new TTS 10 | func newTTS() *TTS { 11 | return &TTS{} 12 | } 13 | 14 | // Speak speaks the given text 15 | func (t *TTS) Speak(text string) error { 16 | return nil 17 | } 18 | 19 | func (t *TTS) getCached() { 20 | data, _ := Cache(nil).Get(context.Background(), "") 21 | if len(data) <= 0 { 22 | return 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /workflow/node/vdb.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/showntop/llmack/workflow" 7 | ) 8 | 9 | // VDBNode ... 10 | type VDBNode struct { 11 | // Node 12 | } 13 | 14 | // NewVDBNode 创建VDBNode 15 | func NewVDBNode(n *workflow.Node) *VDBNode { 16 | return &VDBNode{ 17 | // Node: *n, 18 | } 19 | } 20 | 21 | // Execute 执行VDBNode 22 | func (n *VDBNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) { 23 | 24 | return nil, nil 25 | } 26 | -------------------------------------------------------------------------------- /example/bookkeeper/web/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | server: { 7 | host: '0.0.0.0', 8 | port: 3000, 9 | proxy: { 10 | '/api': { 11 | target: 'http://localhost:8080', 12 | changeOrigin: true, 13 | // rewrite: (path) => path.replace(/^\/api/, ''), 14 | }, 15 | }, 16 | }, 17 | 18 | plugins: [react()], 19 | }) 20 | -------------------------------------------------------------------------------- /tool/datetime/tool.go: -------------------------------------------------------------------------------- 1 | package datetime 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/showntop/llmack/tool" 8 | ) 9 | 10 | const GetTime = "GetTime" 11 | 12 | func init() { 13 | t := tool.New( 14 | tool.WithName(GetTime), 15 | tool.WithKind("code"), 16 | tool.WithDescription("获取当前时间"), 17 | tool.WithFunction(func(ctx context.Context, args string) (string, error) { 18 | return time.Now().Format("2006-01-02 15:04:05"), nil 19 | }), 20 | ) 21 | tool.Register(t) 22 | } 23 | -------------------------------------------------------------------------------- /tool/search/engine/engine.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import "context" 4 | 5 | // Searcher 搜索器 6 | type Searcher interface { 7 | Search(ctx context.Context, query string) ([]*Result, error) 8 | } 9 | 10 | // Result 搜索结果 11 | type Result struct { 12 | Time string `json:"time"` 13 | Link string `json:"link"` 14 | Image string `json:"image"` 15 | Video string `json:"id"` // video id 16 | Title string `json:"title"` 17 | Snippet string `json:"snippet"` 18 | Content string `json:"content"` 19 | } 20 | -------------------------------------------------------------------------------- /prompt/templates/intent.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | var IntentInstruction = ` 4 | You are given an instruction along description of task labelled as [Task Description]. For the given instruction, list out 3-5 keywords in comma separated format as [Intent] which define the characteristics or properties required by the about the most capable and suitable agent to solve the task using the instruction. 5 | 6 | 7 | [Task Description]: {{task_description}} 8 | [Instruction]: {{instruction}} 9 | 10 | 11 | [Intent]: 12 | ` 13 | -------------------------------------------------------------------------------- /program/cot.go: -------------------------------------------------------------------------------- 1 | package program 2 | 3 | // cot chain of thought 4 | type cot struct { 5 | *predictor 6 | } 7 | 8 | // COT ... 9 | func COT() *predictor { 10 | prefix := "Reasoning: Let's think step by step in order to" 11 | desc := "${reasoning}" 12 | 13 | p := &cot{} 14 | p.predictor = Predictor() 15 | p.predictor.WithOutputField("reasoning", desc, prefix) 16 | return p.predictor 17 | } 18 | 19 | func (p *cot) WithRationaleField(infos ...any) *predictor { 20 | p.predictor.WithOutputField(infos...) 21 | return p.predictor 22 | } 23 | -------------------------------------------------------------------------------- /vdb/option.go: -------------------------------------------------------------------------------- 1 | package vdb 2 | 3 | // SearchOption 搜索选项 4 | type SearchOption func(*SearchOptions) 5 | 6 | // SearchOptions 搜索配置 7 | type SearchOptions struct { 8 | TopK int // 返回结果数量限制 9 | Threshold float64 // 相似度阈值 10 | } 11 | 12 | // WithTopk 设置返回结果数量 13 | func WithTopk(topk int) SearchOption { 14 | return func(o *SearchOptions) { 15 | o.TopK = topk 16 | } 17 | } 18 | 19 | // WithThreshold 设置相似度阈值 20 | func WithThreshold(threshold float64) SearchOption { 21 | return func(o *SearchOptions) { 22 | o.Threshold = threshold 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rag/deepdoc/json.go: -------------------------------------------------------------------------------- 1 | package deepdoc 2 | 3 | import ( 4 | "github.com/jeremywohl/flatten" 5 | ) 6 | 7 | // JsonDocument ... 8 | type JsonDocument struct { 9 | } 10 | 11 | // Json ... 12 | // 13 | // nolint:ignore 14 | func Json() *JsonDocument { 15 | return &JsonDocument{} 16 | } 17 | 18 | // Extract ... 19 | func (d *JsonDocument) Extract(filename string, binary []byte) ([]string, error) { 20 | 21 | flat, err := flatten.FlattenString(string(binary), "", flatten.DotStyle) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return []string{flat}, nil 27 | } 28 | -------------------------------------------------------------------------------- /workflow/node/start.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/showntop/llmack/workflow" 7 | ) 8 | 9 | type startNode struct { 10 | executeable 11 | Identifier 12 | } 13 | 14 | // StartNode initializes new start node with start definition 15 | func StartNode(n *workflow.Node) *startNode { 16 | return &startNode{ 17 | Identifier: Identifier{ 18 | id: n.ID, 19 | }, 20 | } 21 | } 22 | 23 | // Exec executes start node 24 | func (f startNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) { 25 | return nil, nil 26 | } 27 | -------------------------------------------------------------------------------- /tool/weather/weather.go: -------------------------------------------------------------------------------- 1 | package weather 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/showntop/llmack/tool" 7 | ) 8 | 9 | const QueryWeather = "QueryWeather" 10 | 11 | func init() { 12 | t := tool.New( 13 | tool.WithName(QueryWeather), 14 | tool.WithKind("code"), 15 | tool.WithDescription("查询天气"), 16 | tool.WithParameters(tool.Parameter{ 17 | Name: "city", Type: tool.String, Required: true, LLMDescrition: "城市", Default: "北京", 18 | }), 19 | tool.WithFunction(func(ctx context.Context, args string) (string, error) { 20 | return "北京晴朗,北风三级,2-15 摄氏度,空气质量优。", nil 21 | }), 22 | ) 23 | tool.Register(t) 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | 27 | # tmp 28 | tmp/ 29 | -------------------------------------------------------------------------------- /prompt/templates/meta.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | // MetaInstruction is the instruction for generating prompts with meta prompts. 4 | var MetaInstruction = ` 5 | You are given a task description and a prompt instruction and different styles known as meta prompts: 6 | [Task Description]: {{task_description}} 7 | [Meta Prompt]: {{meta_prompts}} 8 | Now you need to generate {{num_variations}} variations of following Instruction adaptively mixing meta prompt while keeping similar semantic meaning. 9 | Make sure to wrap each generated prompt with and 10 | [Prompt Instruction]: {{prompt_instruction}} 11 | [Generated Prompts]: 12 | ` 13 | -------------------------------------------------------------------------------- /speech/outbound/outbound.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | type handle func([]byte) error 4 | 5 | // BaseOutbound ... 6 | type BaseOutbound struct { 7 | q chan []byte 8 | sampleRate int 9 | audioEncoding int 10 | } 11 | 12 | // NewBaseOutbound ... 13 | func NewBaseOutbound() *BaseOutbound { 14 | return &BaseOutbound{ 15 | sampleRate: 16000, 16 | audioEncoding: 1, 17 | q: make(chan []byte, 0), 18 | } 19 | } 20 | 21 | // Write ... 22 | func (o *BaseOutbound) Write(data []byte) error { 23 | o.q <- data 24 | return nil 25 | } 26 | 27 | // Reset ... 28 | func (o *BaseOutbound) Reset() error { 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /rag/deepdoc/csv.go: -------------------------------------------------------------------------------- 1 | package deepdoc 2 | 3 | import ( 4 | "bytes" 5 | "encoding/csv" 6 | ) 7 | 8 | // CsvDocument ... 9 | type CsvDocument struct { 10 | } 11 | 12 | // Csv ... 13 | func Csv() *CsvDocument { 14 | return &CsvDocument{} 15 | } 16 | 17 | // Extract ... 18 | func (d *CsvDocument) Extract(filename string, binary []byte) ([]string, error) { 19 | // safe 校验 20 | // read csv values using csv.Reader 21 | csvReader := csv.NewReader(bytes.NewReader(binary)) 22 | csvReader.Comma = ';' 23 | csvReader.LazyQuotes = true 24 | data, err := csvReader.ReadAll() 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | return data[0], nil 30 | } 31 | -------------------------------------------------------------------------------- /speech/entities.go: -------------------------------------------------------------------------------- 1 | package speech 2 | 3 | // TranscriptionPayload ... 4 | type TranscriptionPayload struct { 5 | Text string 6 | Final bool 7 | } 8 | 9 | // TurnPayload ... 10 | type TurnPayload struct { 11 | TurnID string 12 | Transcription *TranscriptionPayload 13 | } 14 | 15 | // AgentPayload ... 16 | type AgentPayload struct { 17 | TurnID string 18 | Transcription TranscriptionPayload 19 | } 20 | 21 | // ResponsePayload ... 22 | type ResponsePayload struct { 23 | Text string 24 | Chunk []byte 25 | First bool 26 | Final bool 27 | } 28 | 29 | // TTSResultPayload ... 30 | type TTSResultPayload struct { 31 | Text string 32 | Stream chan []byte 33 | } 34 | -------------------------------------------------------------------------------- /tool/config.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | 7 | "github.com/showntop/flatmap" 8 | ) 9 | 10 | type config struct { 11 | config *flatmap.Map 12 | once sync.Once 13 | } 14 | 15 | var DefaultConfig config 16 | 17 | func WithConfig(c map[string]any) error { 18 | var err error 19 | DefaultConfig.once.Do(func() { 20 | var mmm *flatmap.Map 21 | mmm, err = flatmap.Flatten(c, flatmap.DefaultTokenizer) 22 | DefaultConfig.config = mmm 23 | }) 24 | return err 25 | } 26 | 27 | func (c *config) GetString(fields ...string) string { 28 | x, _ := c.config.Get(strings.Join(fields, flatmap.DefaultTokenizer.Separator())).(string) 29 | return x 30 | } 31 | -------------------------------------------------------------------------------- /mcp_config/servers.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": { 3 | "filesystem": { 4 | "args": [ 5 | "-y", 6 | "@modelcontextprotocol/server-filesystem", 7 | "/tmp" 8 | ], 9 | "command": "npx", 10 | "description": "文件系统访问服务器", 11 | "enabled": true, 12 | "transport": "stdio" 13 | }, 14 | "github": { 15 | "args": [ 16 | "-y", 17 | "@modelcontextprotocol/server-github" 18 | ], 19 | "command": "npx", 20 | "description": "GitHub仓库访问服务器", 21 | "enabled": false, 22 | "env": { 23 | "GITHUB_PERSONAL_ACCESS_TOKEN": "your-token-here" 24 | }, 25 | "transport": "stdio" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /example/mcp/mcp_config/servers.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": { 3 | "filesystem": { 4 | "args": [ 5 | "-y", 6 | "@modelcontextprotocol/server-filesystem", 7 | "/tmp" 8 | ], 9 | "command": "npx", 10 | "description": "文件系统访问服务器", 11 | "enabled": true, 12 | "transport": "stdio" 13 | }, 14 | "github": { 15 | "args": [ 16 | "-y", 17 | "@modelcontextprotocol/server-github" 18 | ], 19 | "command": "npx", 20 | "description": "GitHub仓库访问服务器", 21 | "enabled": false, 22 | "env": { 23 | "GITHUB_PERSONAL_ACCESS_TOKEN": "your-token-here" 24 | }, 25 | "transport": "stdio" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /llm/logger.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import "context" 4 | 5 | // NoneLogger ... 6 | type NoneLogger struct { 7 | } 8 | 9 | // ErrorContextf ... 10 | func (w *NoneLogger) ErrorContextf(ctx context.Context, format string, args ...interface{}) { 11 | // 在这里实现你的逻辑 12 | } 13 | 14 | // InfoContextf ... 15 | func (w *NoneLogger) InfoContextf(ctx context.Context, format string, args ...interface{}) { 16 | // 在这里实现你的逻辑 17 | } 18 | 19 | // WarnContextf ... 20 | func (w *NoneLogger) WarnContextf(ctx context.Context, format string, args ...interface{}) { 21 | // 在这里实现你的逻辑 22 | } 23 | 24 | // DebugContextf ... 25 | func (w *NoneLogger) DebugContextf(ctx context.Context, format string, args ...interface{}) { 26 | // 在这里实现你的逻辑 27 | } 28 | -------------------------------------------------------------------------------- /rag/deepdoc/docx.go: -------------------------------------------------------------------------------- 1 | package deepdoc 2 | 3 | import ( 4 | "bytes" 5 | 6 | "baliance.com/gooxml/document" 7 | ) 8 | 9 | // DocxDocument ... 10 | type DocxDocument struct { 11 | } 12 | 13 | // Docx ... 14 | func Docx() *DocxDocument { 15 | return &DocxDocument{} 16 | } 17 | 18 | // Extract ... 19 | func (d *DocxDocument) Extract(filename string, binary []byte) ([]string, error) { 20 | doc, err := document.Read(bytes.NewReader(binary), int64(len(binary))) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | sections := []string{} 26 | for _, para := range doc.Paragraphs() { 27 | //run为每个段落相同格式的文字组成的片段 28 | for _, run := range para.Runs() { 29 | sections = append(sections, run.Text()) 30 | } 31 | } 32 | return sections, nil 33 | } 34 | -------------------------------------------------------------------------------- /speech/outbound/file.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/showntop/llmack/speech" 8 | ) 9 | 10 | // FileOutBound for test ... 11 | type FileOutBound struct { 12 | ff *os.File 13 | } 14 | 15 | // NewFileOutBound ... 16 | func NewFileOutBound() speech.Outbound { 17 | f, _ := os.Create("out.wav") 18 | return &FileOutBound{ff: f} 19 | 20 | } 21 | 22 | // Write ... 23 | func (o *FileOutBound) Write(msg []byte) error { 24 | fmt.Println("file outbound write", len(msg)) 25 | _, err := o.ff.Write(msg) 26 | return err 27 | } 28 | 29 | // Reset ... 30 | func (o *FileOutBound) Reset() error { 31 | return nil 32 | } 33 | 34 | // Close ... 35 | func (o *FileOutBound) Close() error { 36 | return o.ff.Close() 37 | } 38 | -------------------------------------------------------------------------------- /example/bookkeeper/web/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "verbatimModuleSyntax": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "erasableSyntaxOnly": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "noUncheckedSideEffectImports": true 23 | }, 24 | "include": ["vite.config.ts"] 25 | } 26 | -------------------------------------------------------------------------------- /example/bookkeeper/web/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /example/bookkeeper/web/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "verbatimModuleSyntax": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "erasableSyntaxOnly": true, 23 | "noFallthroughCasesInSwitch": true, 24 | "noUncheckedSideEffectImports": true 25 | }, 26 | "include": ["src"] 27 | } 28 | -------------------------------------------------------------------------------- /prompt/prompt.go: -------------------------------------------------------------------------------- 1 | // Package prompt handles prompt representation and templating 2 | package prompt 3 | 4 | // Prompt 提示词 5 | type Prompt struct { 6 | Version string 7 | InitialInstruction string // 初始指令,用于生成prompt 8 | Description string // 简短描述任务 9 | Inputs map[string]any // 输入 10 | Outputs any // 输出 11 | Metadata map[string]any // 元数据 12 | } 13 | 14 | // New creates a new prompt 15 | func New() *Prompt { 16 | p := &Prompt{ 17 | Inputs: make(map[string]any), 18 | Metadata: make(map[string]any), 19 | } 20 | return p 21 | } 22 | 23 | // Render renders the prompt as string 24 | func (p *Prompt) Render(inputs map[string]any) string { 25 | xxx, err := Render(p.InitialInstruction, inputs) 26 | if err != nil { 27 | panic(err) 28 | } 29 | return xxx 30 | } 31 | -------------------------------------------------------------------------------- /workflow/workflow.go: -------------------------------------------------------------------------------- 1 | package workflow 2 | 3 | // Result ... 4 | type Result struct { 5 | Outputs map[string]any 6 | } 7 | 8 | // Workflow ... 9 | type Workflow struct { 10 | ID int64 11 | Name string 12 | Description string 13 | Key string 14 | Version int64 15 | Nodes []Node 16 | Edges []Edge 17 | // Metadata metadata 18 | } 19 | 20 | // Parameters ... 21 | type Parameters map[string]Parameter 22 | 23 | // Parameter ... 24 | type Parameter struct { 25 | Name string `json:"name"` // 参数名称 26 | Value string `json:"value"` // 参数值 27 | Type string `json:"type"` // 参数类型 28 | } 29 | 30 | // Nodes ... 31 | type Nodes []Node 32 | 33 | // Event ... 34 | type Event struct { 35 | Name string `json:"name"` // 事件名称 36 | Data any `json:"data"` // 事件值 37 | Type string `json:"type"` // 事件类型 38 | } 39 | -------------------------------------------------------------------------------- /rag/retirval.go: -------------------------------------------------------------------------------- 1 | package rag 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/showntop/llmack/log" 7 | "github.com/showntop/llmack/vdb" 8 | ) 9 | 10 | // Retrival ... 11 | type Retrival struct { 12 | vdb vdb.VDB 13 | scalarDB ScalarDB // object 14 | } 15 | 16 | // NewRetrival ... 17 | func NewRetrival(name string, config any) (*Retrival, error) { 18 | // new vdb 19 | vdb, err := vdb.NewVDB(name, config) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | return &Retrival{vdb: vdb}, nil 25 | } 26 | 27 | // Retrieve TODO: 实现检索逻辑 28 | func (r *Retrival) Retrieve(ctx context.Context, query string, opts *SearchOptions) ([]*vdb.Document, error) { 29 | log.InfoContextf(ctx, "Retrieve knowledge: %v query: %s options: %+v", opts.LibraryID, query, opts) 30 | 31 | // return r.vdb.Search(ctx, query, opts) 32 | return nil, nil 33 | } 34 | -------------------------------------------------------------------------------- /example/llm/moonshot/moonshot.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | 10 | "github.com/showntop/llmack/llm" 11 | "github.com/showntop/llmack/llm/moonshot" 12 | ) 13 | 14 | func init() { 15 | godotenv.Load() 16 | } 17 | 18 | func main() { 19 | ctx := context.Background() 20 | llm.WithSingleConfig(map[string]any{ 21 | "api_key": os.Getenv("moonshot_api_key"), 22 | }) 23 | 24 | resp, err := llm.NewInstance(moonshot.Name).Invoke(ctx, []llm.Message{ 25 | llm.NewUserTextMessage("你好"), 26 | }, llm.WithModel("moonshot-v1-8k")) 27 | if err != nil { 28 | panic(err) 29 | } 30 | // stream := resp.Stream() 31 | // for v := stream.Next(); v != nil; v = stream.Next() { 32 | // fmt.Println(string(v.Delta.Message.Content().Data)) 33 | // } 34 | fmt.Println(resp.Result()) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /rag/rag.go: -------------------------------------------------------------------------------- 1 | package rag 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/showntop/llmack/vdb" 7 | ) 8 | 9 | // ScalarDB ... 10 | type ScalarDB interface { 11 | Fetch(context.Context, string, *SearchOptions) (*vdb.Document, error) 12 | } 13 | 14 | // Options ... 15 | type SearchOptions struct { 16 | LibraryID int64 `json:"id"` 17 | Kind string `json:"kind"` 18 | IndexID int32 `json:"index_id"` 19 | TopK int `json:"top_k"` 20 | ScoreThreshold float64 `json:"score_threshold"` 21 | } 22 | 23 | type SearchOption func(*SearchOptions) 24 | 25 | func WithTopK(topK int) SearchOption { 26 | return func(o *SearchOptions) { 27 | o.TopK = topK 28 | } 29 | } 30 | 31 | func WithScoreThreshold(scoreThreshold float64) SearchOption { 32 | return func(o *SearchOptions) { 33 | o.ScoreThreshold = scoreThreshold 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/bookkeeper/web/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /example/bookkeeper/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/spf13/viper" 5 | ) 6 | 7 | type Config struct { 8 | Server ServerConfig 9 | Database DatabaseConfig 10 | LLM LLMConfig 11 | } 12 | 13 | type ServerConfig struct { 14 | Port string 15 | } 16 | 17 | type DatabaseConfig struct { 18 | Host string 19 | Port int 20 | User string 21 | Password string 22 | DBName string 23 | } 24 | 25 | type LLMConfig struct { 26 | Provider string 27 | Model string 28 | } 29 | 30 | func LoadConfig() (*Config, error) { 31 | viper.SetConfigName("config") 32 | viper.SetConfigType("yaml") 33 | viper.AddConfigPath("./config") 34 | 35 | if err := viper.ReadInConfig(); err != nil { 36 | return nil, err 37 | } 38 | 39 | var config Config 40 | if err := viper.Unmarshal(&config); err != nil { 41 | return nil, err 42 | } 43 | 44 | return &config, nil 45 | } 46 | -------------------------------------------------------------------------------- /example/bookkeeper/models/transaction.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type Transaction struct { 10 | gorm.Model 11 | Amount float64 `json:"amount"` 12 | Category string `json:"category"` 13 | Description string `json:"description"` 14 | Date time.Time `json:"date"` 15 | ImageURL string `json:"image_url"` 16 | UserID uint `json:"user_id"` 17 | } 18 | 19 | type Category struct { 20 | gorm.Model 21 | Name string `json:"name"` 22 | Description string `json:"description"` 23 | Type string `json:"type"` // income or expense 24 | } 25 | 26 | type Budget struct { 27 | gorm.Model 28 | Category string `json:"category"` 29 | Amount float64 `json:"amount"` 30 | StartDate time.Time `json:"start_date"` 31 | EndDate time.Time `json:"end_date"` 32 | UserID uint `json:"user_id"` 33 | } 34 | -------------------------------------------------------------------------------- /example/llm/ollama/ollama.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/showntop/llmack/llm" 8 | _ "github.com/showntop/llmack/llm/ollama" 9 | ) 10 | 11 | func main() { 12 | ctx := context.Background() 13 | 14 | // llm.WithConfigs(map[string]any{ 15 | // "ollama": map[string]any{ 16 | // "endpoint": "http://127.0.0.1:4000", 17 | // }, 18 | // }) 19 | 20 | llm.WithSingleConfig(map[string]any{ 21 | "base_url": "http://127.0.0.1:11434", 22 | }) 23 | 24 | resp, err := llm.NewInstance("ollama").Invoke(ctx, []llm.Message{ 25 | llm.NewUserTextMessage("你好"), 26 | }, llm.WithModel("llama3.1"), llm.WithStream(true)) 27 | if err != nil { 28 | panic(err) 29 | } 30 | // stream := resp.Stream() 31 | // for v := stream.Next(); v != nil; v = stream.Next() { 32 | // fmt.Println(string(v.Delta.Message.Content().Data)) 33 | // } 34 | fmt.Println(resp.Result()) 35 | } 36 | -------------------------------------------------------------------------------- /llm/cache_metrics.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "sync/atomic" 5 | "time" 6 | ) 7 | 8 | // Metrics 存储缓存的统计信息 9 | type Metrics struct { 10 | hits uint64 11 | misses uint64 12 | adds uint64 13 | evicts uint64 14 | lastAccess time.Time 15 | } 16 | 17 | func (m *Metrics) recordHit() { atomic.AddUint64(&m.hits, 1) } 18 | func (m *Metrics) recordMiss() { atomic.AddUint64(&m.misses, 1) } 19 | func (m *Metrics) recordAdd() { atomic.AddUint64(&m.adds, 1) } 20 | func (m *Metrics) recordEvict() { atomic.AddUint64(&m.evicts, 1) } 21 | 22 | // GetStats 返回当前统计信息 23 | func (m *Metrics) GetStats() map[string]interface{} { 24 | return map[string]interface{}{ 25 | "hits": atomic.LoadUint64(&m.hits), 26 | "misses": atomic.LoadUint64(&m.misses), 27 | "adds": atomic.LoadUint64(&m.adds), 28 | "evicts": atomic.LoadUint64(&m.evicts), 29 | "last_access": m.lastAccess, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/llm/zhipu/zhipu.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | 10 | "github.com/showntop/llmack/llm" 11 | "github.com/showntop/llmack/llm/zhipu" 12 | ) 13 | 14 | func init() { 15 | godotenv.Load() 16 | } 17 | 18 | func main() { 19 | ctx := context.Background() 20 | llm.WithSingleConfig(map[string]any{ 21 | "api_key": os.Getenv("zhipu_api_key"), 22 | }) 23 | 24 | resp, err := llm.NewInstance(zhipu.Name).Invoke(ctx, 25 | []llm.Message{ 26 | // llm.UserTextPromptMessage("你好"), 27 | llm.NewUserMultipartMessage( 28 | llm.MultipartContentImageURL("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"), 29 | llm.MultipartContentText("这是一张关于猫的照片吗"), 30 | ), 31 | }, 32 | 33 | llm.WithModel("glm-4v-plus")) 34 | if err != nil { 35 | panic(err) 36 | } 37 | fmt.Println(resp.Result()) 38 | } 39 | -------------------------------------------------------------------------------- /speech/outbound/ratelimit.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // RateLimitOutbound ... 8 | type RateLimitOutbound struct { 9 | *BaseOutbound 10 | handle func([]byte) error 11 | } 12 | 13 | // NewRateLimitOutbound ... 14 | func NewRateLimitOutbound() *RateLimitOutbound { 15 | out := &RateLimitOutbound{} 16 | out.BaseOutbound = NewBaseOutbound() 17 | go out.Loop() 18 | return out 19 | } 20 | 21 | // Loop ... 22 | func (o *RateLimitOutbound) Loop() error { 23 | for chunk := range o.q { 24 | startTime := time.Now() 25 | // 置换 chunk 状态 26 | chunkSizePerDuration := float64(o.sampleRate) * 2.00 / float64(time.Second) 27 | durations := time.Duration(float64(len(chunk)) / chunkSizePerDuration) 28 | o.handle(chunk) 29 | endTime := time.Now() 30 | 31 | x := durations - endTime.Sub(startTime) - 10*time.Millisecond 32 | if x > 0 { 33 | time.Sleep(x) 34 | } 35 | 36 | } 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /tool/file/create_file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "os" 7 | 8 | "github.com/showntop/llmack/tool" 9 | ) 10 | 11 | const CreateFile = "CreateFile" 12 | 13 | func init() { 14 | t := tool.New( 15 | tool.WithName(CreateFile), 16 | tool.WithKind("code"), 17 | tool.WithDescription("创建文件"), 18 | tool.WithParameters(tool.Parameter{ 19 | Name: "path", 20 | LLMDescrition: "文件路径", 21 | Type: tool.String, 22 | Required: true, 23 | }), 24 | tool.WithFunction(func(ctx context.Context, args string) (string, error) { 25 | var params struct { 26 | Path string `json:"path"` 27 | } 28 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil { 29 | return "", err 30 | } 31 | _, err := os.Create(params.Path) 32 | if err != nil { 33 | return "", err 34 | } 35 | return "文件创建成功", nil 36 | }), 37 | ) 38 | tool.Register(t) 39 | } 40 | -------------------------------------------------------------------------------- /example/bookkeeper/database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/bookkeeper-ai/bookkeeper/config" 8 | "github.com/bookkeeper-ai/bookkeeper/models" 9 | "gorm.io/driver/postgres" 10 | "gorm.io/gorm" 11 | ) 12 | 13 | var DB *gorm.DB 14 | 15 | func InitDB(cfg *config.Config) error { 16 | dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", 17 | cfg.Database.Host, 18 | cfg.Database.Port, 19 | cfg.Database.User, 20 | cfg.Database.Password, 21 | cfg.Database.DBName, 22 | ) 23 | 24 | db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | DB = db 30 | 31 | // 自动迁移数据库表 32 | err = DB.AutoMigrate(&models.Transaction{}, &models.Category{}, &models.Budget{}, &models.User{}) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | log.Println("Database connected successfully") 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /speech/asr/tencent/option.go: -------------------------------------------------------------------------------- 1 | package tencent 2 | 3 | import "github.com/showntop/tencentcloud-speech-sdk-go/asr" 4 | 5 | // Option ... 6 | type Option func(*Options) 7 | 8 | // Options ... 9 | type Options struct { 10 | AppID string 11 | SecretID string 12 | SecretKey string 13 | 14 | listener asr.SpeechRecognitionListener 15 | } 16 | 17 | // WithAppID ... 18 | func WithAppID(appID string) Option { 19 | return func(o *Options) { 20 | o.AppID = appID 21 | } 22 | } 23 | 24 | // WithSecretID ... 25 | func WithSecretID(secretID string) Option { 26 | return func(o *Options) { 27 | o.SecretID = secretID 28 | } 29 | } 30 | 31 | // WithSecretKey ... 32 | func WithSecretKey(secretKey string) Option { 33 | return func(o *Options) { 34 | o.SecretKey = secretKey 35 | } 36 | } 37 | 38 | // WithListener ... 39 | func WithListener(l asr.SpeechRecognitionListener) Option { 40 | return func(o *Options) { 41 | o.listener = l 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/structx/set.go: -------------------------------------------------------------------------------- 1 | package structx 2 | 3 | type set[T comparable] struct { 4 | keys map[T]int 5 | values []any 6 | } 7 | 8 | // Set ... 9 | func Set[T comparable]() *set[T] { 10 | return &set[T]{ 11 | keys: make(map[T]int), 12 | values: make([]any, 0), 13 | } 14 | } 15 | 16 | func (s *set[T]) Set(key T, value any) { 17 | if x, ok := s.keys[key]; ok { 18 | s.values[x] = value 19 | return 20 | } 21 | s.keys[key] = len(s.values) 22 | s.values = append(s.values, value) 23 | } 24 | 25 | func (s *set[T]) Get(key T) any { 26 | 27 | x, ok := s.keys[key] 28 | if !ok { 29 | return nil 30 | } 31 | return s.values[x] 32 | } 33 | 34 | func (s *set[T]) Del(key T, value any) { 35 | 36 | } 37 | 38 | func (s *set[T]) Exist(key T) bool { 39 | _, ok := s.keys[key] 40 | return ok 41 | } 42 | 43 | func (s *set[T]) Values() []any { 44 | return s.values 45 | } 46 | 47 | func (s *set[T]) SortBy(field string) []any { 48 | return s.values 49 | } 50 | -------------------------------------------------------------------------------- /speech/tts/cache.go: -------------------------------------------------------------------------------- 1 | package tts 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/redis/go-redis/v9" 8 | ) 9 | 10 | var onceCache sync.Once 11 | 12 | // redisCache is the struct for the redis 13 | type redisCache struct { 14 | client *redis.Client 15 | } 16 | 17 | var defaultCache *redisCache 18 | 19 | // Cache creates a new redis cache 20 | func Cache(cli *redis.Client) *redisCache { 21 | onceCache.Do(func() { 22 | cli := redis.NewClient(&redis.Options{ 23 | Addr: "localhost:6379", 24 | Password: "", // no password set 25 | DB: 0, // use default DB 26 | }) 27 | defaultCache = &redisCache{client: cli} 28 | }) 29 | return defaultCache 30 | } 31 | 32 | func (c *redisCache) Get(ctx context.Context, key string) ([]byte, error) { 33 | return c.client.Get(ctx, key).Bytes() 34 | } 35 | 36 | func (c *redisCache) Set(ctx context.Context, key string, raw []byte) error { 37 | return c.client.Set(ctx, key, raw, 0).Err() 38 | } 39 | -------------------------------------------------------------------------------- /program/option.go: -------------------------------------------------------------------------------- 1 | package program 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/showntop/llmack/llm" 7 | ) 8 | 9 | type option func(*predictor) 10 | 11 | func WithAdapter(adapter Adapter) option { 12 | return func(p *predictor) { 13 | p.adapter = adapter 14 | } 15 | } 16 | 17 | func WithLLM(provider string, model string, opts ...llm.Option) option { 18 | return func(p *predictor) { 19 | opts = append(opts, llm.WithDefaultModel(model)) 20 | p.model = llm.NewInstance(provider, opts...) 21 | } 22 | } 23 | 24 | func WithLLMInstance(model *llm.Instance) option { 25 | return func(p *predictor) { 26 | p.model = model 27 | } 28 | } 29 | 30 | func WithMaxIterationNum(num int) option { 31 | return func(p *predictor) { 32 | p.maxIterationNum = num 33 | } 34 | } 35 | 36 | func WithResetMessages(resetMessages func(ctx context.Context, messages []llm.Message) []llm.Message) option { 37 | return func(p *predictor) { 38 | p.resetMessages = resetMessages 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /speech/tts/tencent/stream.go: -------------------------------------------------------------------------------- 1 | package tencent 2 | 3 | import ( 4 | "sync" 5 | 6 | mtts "github.com/showntop/llmack/speech/tts" 7 | "github.com/showntop/tencentcloud-speech-sdk-go/tts" 8 | ) 9 | 10 | // StreamTTS ... 11 | type StreamTTS struct { 12 | sync.Mutex 13 | 14 | mtts.TTS 15 | options Options 16 | client *tts.SpeechWsv2Synthesizer 17 | sessionID string 18 | buffer chan []byte 19 | } 20 | 21 | func NewStreamTTS(opts ...Option) *StreamTTS { 22 | return &StreamTTS{} 23 | } 24 | 25 | func (s *StreamTTS) Complete() error { 26 | return nil 27 | } 28 | 29 | func (s *StreamTTS) Prepare() error { 30 | return nil 31 | } 32 | 33 | func (s *StreamTTS) Input(text string) error { 34 | return nil 35 | } 36 | 37 | func (s *StreamTTS) Synthesize() error { 38 | return nil 39 | } 40 | 41 | func (s *StreamTTS) Terminate() error { 42 | return nil 43 | } 44 | 45 | // StreamResult() chan []byte 46 | func (s *StreamTTS) StreamResult() chan []byte { 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /workflow/node/expr.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/expr-lang/expr" 8 | 9 | "github.com/showntop/llmack/workflow" 10 | wf "github.com/showntop/llmack/workflow" 11 | ) 12 | 13 | // exprNode TODO 14 | type exprNode struct { 15 | executeable 16 | Identifier 17 | 18 | Node *wf.Node 19 | } 20 | 21 | // ExprNode 创建expr node 22 | func ExprNode(n *workflow.Node) *exprNode { 23 | return &exprNode{ 24 | Node: n, 25 | } 26 | } 27 | 28 | // Execute 执行JSON节点,单次执行 29 | func (n *exprNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) { 30 | 31 | expression, _ := n.Node.Metadata["expr"].(string) 32 | if expression == "" { 33 | return nil, nil 34 | } 35 | 36 | program, err := expr.Compile(expression) 37 | if err != nil { 38 | return nil, errors.Join(err) 39 | } 40 | result, err := expr.Run(program, r.Inputs) 41 | if err != nil { 42 | return nil, errors.Join(err) 43 | } 44 | 45 | return result, nil 46 | } 47 | -------------------------------------------------------------------------------- /tool/user/inquiry.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/showntop/llmack/tool" 9 | ) 10 | 11 | const Inquery = "Inquery" 12 | 13 | func init() { 14 | t := tool.New( 15 | tool.WithName(Inquery), 16 | tool.WithKind("code"), 17 | tool.WithDescription("询问用户的具体需求以提供个性化的服务"), 18 | tool.WithParameters(tool.Parameter{ 19 | Name: "inquiry", 20 | LLMDescrition: "询问用户的具体需求以提供个性化的服务", 21 | Required: true, 22 | Type: tool.String, 23 | }), 24 | tool.WithFunction(func(ctx context.Context, args string) (string, error) { 25 | var params struct { 26 | Inquiry string `json:"inquiry"` 27 | } 28 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil { 29 | return "", err 30 | } 31 | // 等待用户输入 32 | var input string 33 | fmt.Printf("请根据提示输入你的需求 (%s): ", params.Inquiry) 34 | fmt.Scanf("%s", &input) 35 | return input, nil 36 | }), 37 | ) 38 | tool.Register(t) 39 | } 40 | -------------------------------------------------------------------------------- /workflow/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/showntop/llmack/workflow" 7 | ) 8 | 9 | // Identifier TODO 10 | type Identifier struct{ id string } 11 | 12 | // ID TODO 13 | func (i *Identifier) ID() string { return i.id } 14 | 15 | // SetID TODO 16 | func (i *Identifier) SetID(id string) { i.id = id } 17 | 18 | type ( 19 | executeable struct{} 20 | 21 | // Nodes TODO 22 | Nodes []Node 23 | 24 | // Node TODO 25 | Node interface { 26 | ID() string 27 | SetID(string) 28 | Execute(context.Context, *ExecRequest) (ExecResponse, error) 29 | } 30 | 31 | execFn func(context.Context, *ExecRequest) (ExecResponse, error) 32 | ) 33 | 34 | type ExecRequest struct { 35 | ProcessID string 36 | WorkflowID string 37 | NodeID string 38 | Sponsor string 39 | Runner string // 运行人 40 | Inputs map[string]any 41 | Scope map[string]any 42 | // Graph *dag.Graph 43 | Events chan *workflow.Event 44 | } 45 | 46 | type ExecResponse any 47 | -------------------------------------------------------------------------------- /example/deepdoc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strings" 8 | 9 | "github.com/showntop/llmack/rag/deepdoc/chunk" 10 | "github.com/showntop/llmack/rag/deepdoc/extractor" 11 | 12 | pstrings "github.com/showntop/llmack/pkg/strings" 13 | ) 14 | 15 | func main() { 16 | 17 | file, err := os.OpenFile( 18 | "example/deepdoc/企业级 SaaS 行业增长白皮书.pdf", 19 | os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) 20 | if err != nil { 21 | panic(err) 22 | } 23 | content, _ := io.ReadAll(file) 24 | sections, err := extractor.Extract( 25 | &extractor.Meta{Filename: "企业级 SaaS 行业增长白皮书.pdf"}, content, "doc") 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | chunker := chunk.NewCharacterChunker(500, 50, "") 31 | chunks, _ := chunker.Chunk(string(strings.Join(sections, "\n"))) 32 | fmt.Println(len(chunks)) 33 | for i := 0; i < len(chunks); i++ { 34 | fmt.Println("--------------------") 35 | fmt.Printf("%s\n", pstrings.TrimSpecial(chunks[i])) 36 | fmt.Println(len([]rune(pstrings.TrimSpecial(chunks[i])))) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /speech/asr/mock.go: -------------------------------------------------------------------------------- 1 | package asr 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // MockASR MockASR 8 | type MockASR struct { 9 | current int 10 | contents []string 11 | calback func(string, bool) error 12 | } 13 | 14 | // NewMockASR 新建一个MockASR 15 | func NewMockASR(c func(string, bool) error) *MockASR { 16 | t := &MockASR{calback: c} 17 | // t.contents = []string{"你好,", "请问怎么", "处理广告", "不起量的", "问题。"} 18 | t.contents = []string{"你是谁。"} 19 | return t 20 | } 21 | 22 | // Input 实现语音识别,转录为文字 23 | func (t *MockASR) Input(content []byte) error { 24 | if len(content) == 0 { 25 | return nil 26 | } 27 | if t.current >= len(t.contents) { 28 | return nil 29 | } 30 | 31 | ssss := t.contents[t.current] 32 | final := false 33 | if t.current == len(t.contents)-1 { 34 | ssss = strings.Join(t.contents, "") 35 | final = true 36 | // t.current = 0 37 | } 38 | 39 | t.calback(ssss, final) 40 | t.current++ 41 | return nil 42 | } 43 | 44 | // Recognize 实现语音识别,转录为文字 45 | func (t *MockASR) Recognize(content []byte) (string, error) { 46 | return "", nil 47 | } 48 | -------------------------------------------------------------------------------- /llm/qwen/llm.go: -------------------------------------------------------------------------------- 1 | package qwen 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/showntop/llmack/llm" 9 | ) 10 | 11 | const ( 12 | // Name name of llm 13 | Name = "qwen" 14 | ) 15 | 16 | func init() { 17 | llm.Register(Name, func(o *llm.ProviderOptions) llm.Provider { return &LLM{} }) 18 | } 19 | 20 | // LLM TODO 21 | type LLM struct { 22 | once sync.Once 23 | engine *llm.OAILLM 24 | } 25 | 26 | var initOnce sync.Once 27 | 28 | // Invoke TODO 29 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) { 30 | var err error 31 | m.once.Do(func() { 32 | url := "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions" 33 | config, _ := llm.Config.Get(Name).(map[string]any) 34 | if config == nil { 35 | err = fmt.Errorf(Name + " config not found") 36 | } 37 | apiKey, _ := config["api_key"].(string) 38 | m.engine = llm.NewOAILLM(url, apiKey) 39 | }) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return m.engine.Invoke(ctx, messages, opts) 44 | } 45 | -------------------------------------------------------------------------------- /program/program.go: -------------------------------------------------------------------------------- 1 | package program 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/showntop/llmack/llm" 7 | "github.com/showntop/llmack/llm/deepseek" 8 | ) 9 | 10 | // var defaultLLM = llm.NewInstance(moonshot.Name, llm.WithDefaultModel("moonshot-v1-8k")) 11 | // var defaultLLM = llm.NewInstance(hunyuan.Name, llm.WithDefaultModel("hunyuan")) 12 | // var defaultLLM = llm.NewInstance(zhipu.Name, llm.WithDefaultModel("GLM-4-Flash")) 13 | var defaultLLM = llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat")) 14 | 15 | // SetLLM can not set concurrent 16 | func SetLLM(provider, model string) { 17 | defaultLLM = llm.NewInstance(provider, llm.WithDefaultModel(model)) 18 | } 19 | 20 | // Program defines the interface for a program 21 | // Program 定义了一个可运行的程序,不会保存状态,随用随 new 22 | type Program interface { 23 | Prompt() *Promptx 24 | Update(...option) 25 | Forward(context.Context, map[string]any) (any, error) 26 | } 27 | 28 | // ProgramFunc is a function that implements the Program interface 29 | type ProgramFunc func(context.Context, string) (string, error) 30 | -------------------------------------------------------------------------------- /rag/deepdoc/pptx.go: -------------------------------------------------------------------------------- 1 | package deepdoc 2 | 3 | import ( 4 | "bytes" 5 | 6 | "baliance.com/gooxml/presentation" 7 | ) 8 | 9 | // PptxDocument ... 10 | type PptxDocument struct { 11 | } 12 | 13 | // Pptx ... 14 | func Pptx() *PptxDocument { 15 | return &PptxDocument{} 16 | } 17 | 18 | // Extract ... 19 | func (d *PptxDocument) Extract(filename string, binary []byte) ([]string, error) { 20 | doc, err := presentation.Read(bytes.NewReader(binary), int64(len(binary))) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | sections := make([]string, 0) 26 | 27 | for _, slide := range doc.Slides() { 28 | //run为每个段落相同格式的文字组成的片段 29 | for _, choice := range slide.X().CSld.SpTree.Choice { 30 | if choice.Sp == nil { 31 | continue 32 | } 33 | for _, sp := range choice.Sp { 34 | for _, p := range sp.TxBody.P { 35 | for _, run := range p.EG_TextRun { 36 | if run.R == nil { 37 | continue 38 | } 39 | sections = append(sections, run.R.T) 40 | } 41 | } 42 | } 43 | } 44 | } 45 | 46 | return sections, nil 47 | } 48 | -------------------------------------------------------------------------------- /storage/session.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/showntop/llmack/llm" 7 | "github.com/showntop/llmack/memory" 8 | ) 9 | 10 | // for domain usage 11 | type Session struct { 12 | UID string `json:"uid" gorm:"column:uid;type:varchar(255);"` // session id 13 | EngineID string `json:"engine_id" gorm:"column:engine_id;type:varchar(255);"` // engine id 14 | EngineType string `json:"engine_type" gorm:"column:engine_type;type:varchar(255);"` // engine type 15 | EngineData map[string]any `json:"engine_data" gorm:"column:engine_data;type:jsonb"` // engine data 16 | 17 | Memory memory.Memory `json:"memory"` // 记忆 18 | 19 | Messages []llm.Message `json:"messages"` 20 | 21 | Data any `json:"data"` 22 | 23 | CreatedAt time.Time `json:"created_at" gorm:"column:created_at;autoCreateTime"` 24 | UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at;autoUpdateTime"` 25 | } 26 | 27 | func NewSession(id string) *Session { 28 | return &Session{ 29 | UID: id, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /workflow/node/factory.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/showntop/llmack/workflow" 7 | ) 8 | 9 | // Build 根据节点类型构建节点 10 | func Build(n *workflow.Node, outgoing ...*workflow.Edge) (Node, error) { 11 | 12 | switch n.Kind { 13 | case workflow.NodeKindStart: 14 | return StartNode(n), nil 15 | case workflow.NodeKindLLM: 16 | return LLMNode(n), nil 17 | case workflow.NodeKindGateway: 18 | switch n.Subref { 19 | case "exclusive": 20 | return ExclGateway(n, outgoing...) 21 | case "inclusive": 22 | return ForkGateway(n, outgoing...) 23 | case "parallel": 24 | return ForkGateway(n, outgoing...) 25 | } 26 | case workflow.NodeKindWait: 27 | // return WaitNode(n) 28 | case workflow.NodeKindHuman: 29 | switch n.Subref { 30 | case "approval": 31 | case "human": 32 | } 33 | case workflow.NodeKindEnd: 34 | return EndNode(n) 35 | case workflow.NodeKindTool: 36 | return ToolNode(n) 37 | case workflow.NodeKindExpr: 38 | return ExprNode(n), nil 39 | } 40 | return nil, fmt.Errorf("unknown workflow node kind %q", n.Kind) 41 | } 42 | -------------------------------------------------------------------------------- /example/bookkeeper/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookkeeper-web", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@emotion/react": "^11.14.0", 14 | "@emotion/styled": "^11.14.0", 15 | "@mui/icons-material": "^7.1.0", 16 | "@mui/material": "^7.1.0", 17 | "@types/recharts": "^1.8.29", 18 | "axios": "^1.9.0", 19 | "react": "^19.1.0", 20 | "react-dom": "^19.1.0", 21 | "recharts": "^2.15.3" 22 | }, 23 | "devDependencies": { 24 | "@eslint/js": "^9.25.0", 25 | "@types/react": "^19.1.2", 26 | "@types/react-dom": "^19.1.2", 27 | "@vitejs/plugin-react": "^4.4.1", 28 | "eslint": "^9.25.0", 29 | "eslint-plugin-react-hooks": "^5.2.0", 30 | "eslint-plugin-react-refresh": "^0.4.19", 31 | "globals": "^16.0.0", 32 | "typescript": "~5.8.3", 33 | "typescript-eslint": "^8.30.1", 34 | "vite": "^6.3.5" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/llm/vision/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | "github.com/showntop/llmack/llm" 10 | openaic "github.com/showntop/llmack/llm/openai-c" 11 | ) 12 | 13 | func init() { 14 | godotenv.Load() 15 | } 16 | 17 | func main() { 18 | ctx := context.Background() 19 | 20 | llm.WithSingleConfig(map[string]any{ 21 | "base_url": os.Getenv("hunyuan_base_url"), 22 | "api_key": os.Getenv("hunyuan_api_key"), 23 | }) 24 | 25 | resp, err := llm.NewInstance(openaic.Name).Invoke(ctx, []llm.Message{ 26 | llm.NewUserMultipartMessage( 27 | llm.MultipartContentImageURL("https://img.tukuppt.com/bg_grid/05/37/54/v40ZCaqERa.jpg!/fh/350"), 28 | llm.MultipartContentText("给这张图片添加一个太阳"), 29 | ), 30 | }, 31 | llm.WithModel("hunyuan-vision"), 32 | llm.WithStream(true), 33 | ) 34 | 35 | if err != nil { 36 | panic(err) 37 | } 38 | for it := resp.Stream().Take(); it != nil; it = resp.Stream().Take() { 39 | if len(it.Choices) > 0 { 40 | fmt.Println("final: ", it.Choices[0].Delta.String()) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /speech/outbound/websocket.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "encoding/base64" 5 | "os" 6 | 7 | "github.com/gorilla/websocket" 8 | "github.com/showntop/llmack/speech" 9 | ) 10 | 11 | // WsOutbound ... 12 | type WsOutbound struct { 13 | *RateLimitOutbound 14 | ws *websocket.Conn 15 | 16 | ff *os.File 17 | } 18 | 19 | // NewWsOutbound ... 20 | func NewWsOutbound(ws *websocket.Conn) speech.Outbound { 21 | out := &WsOutbound{ws: ws} 22 | out.RateLimitOutbound = NewRateLimitOutbound() 23 | out.handle = out.write 24 | out.ff, _ = os.Create("out-ws.wav") 25 | return out 26 | } 27 | 28 | func (o *WsOutbound) write(data []byte) error { 29 | rr := base64.StdEncoding.EncodeToString(data) 30 | o.ff.Write([]byte(rr)) 31 | o.ff.Write([]byte{'\n'}) 32 | // log.InfoContextf(context.TODO(), "message %s, out", rr[:10]) 33 | // return o.ws.WriteMessage(websocket.BinaryMessage, data) 34 | return o.ws.WriteJSON(map[string]any{ 35 | "type": "websocket_audio", 36 | "data": rr, 37 | }) 38 | } 39 | 40 | // Close ... 41 | func (o *WsOutbound) Close() error { 42 | return o.ws.Close() 43 | } 44 | -------------------------------------------------------------------------------- /rag/deepdoc/xlsx.go: -------------------------------------------------------------------------------- 1 | package deepdoc 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/xuri/excelize/v2" 8 | ) 9 | 10 | // XlsxDocument ... 11 | type XlsxDocument struct { 12 | } 13 | 14 | // Excel ... 15 | func Excel() *XlsxDocument { 16 | return &XlsxDocument{} 17 | } 18 | 19 | // Extract ... 20 | func (d *XlsxDocument) Extract(filename string, binary []byte) ([]string, error) { 21 | doc, err := excelize.OpenReader(bytes.NewReader(binary)) 22 | if err != nil { 23 | return nil, err 24 | } 25 | if len(doc.GetSheetList()) <= 0 { 26 | return nil, fmt.Errorf("empty excel") 27 | } 28 | sheet := doc.GetSheetList()[0] 29 | 30 | sections := []string{} 31 | rows, err := doc.GetRows(sheet) 32 | if err != nil { 33 | return nil, err 34 | } 35 | for i, row := range rows { 36 | for j := range row { 37 | cell, _ := excelize.CoordinatesToCellName(j+1, i+1) 38 | value, _ := doc.GetCellValue(sheet, cell) 39 | 40 | _, link, _ := doc.GetCellHyperLink(sheet, cell) 41 | value += link 42 | sections = append(sections, value) 43 | } 44 | } 45 | 46 | return sections, nil 47 | } 48 | -------------------------------------------------------------------------------- /example/llm/doubao/doubao.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/joho/godotenv" 9 | 10 | "github.com/showntop/llmack/llm" 11 | "github.com/showntop/llmack/llm/doubao" 12 | ) 13 | 14 | func init() { 15 | godotenv.Load() 16 | } 17 | 18 | func main() { 19 | ctx := context.Background() 20 | llm.WithSingleConfig(map[string]any{ 21 | // "api_key": os.Getenv("doubao_api_key"), 22 | "base_url": "https://ark.cn-beijing.volces.com/api/v3", 23 | }) 24 | 25 | resp, err := llm.NewInstance(doubao.Name).Invoke(ctx, 26 | // []llm.Message{llm.UserPromptMessage("Prove that all entire functions that are also injective take the form f (z) = az + 6 with a, b € C, and a ‡ 0.")}, 27 | []llm.Message{llm.NewUserTextMessage("你好")}, 28 | llm.WithModel("doubao-1.5-ui-tars-250328"), 29 | llm.WithStream(true), 30 | ) 31 | if err != nil { 32 | panic(err) 33 | } 34 | for it := resp.Stream().Take(); it != nil; it = resp.Stream().Take() { 35 | xxx, _ := json.Marshal(it) 36 | fmt.Println(string(xxx)) 37 | // fmt.Println("final: ", it.Delta.Message) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /speech/tts/tencent/options.go: -------------------------------------------------------------------------------- 1 | package tencent 2 | 3 | import "github.com/showntop/tencentcloud-speech-sdk-go/tts" 4 | 5 | // Options ... 6 | type Options struct { 7 | secretID string 8 | secretKey string 9 | appID int64 10 | longText bool 11 | Listener tts.SpeechWsv2SynthesisListener 12 | } 13 | 14 | // Option ... 15 | type Option func(*Options) 16 | 17 | // WithSecretID ... 18 | func WithSecretID(secretID string) Option { 19 | return func(o *Options) { 20 | o.secretID = secretID 21 | } 22 | } 23 | 24 | // WithSecretKey ... 25 | func WithSecretKey(secretKey string) Option { 26 | return func(o *Options) { 27 | o.secretKey = secretKey 28 | } 29 | } 30 | 31 | // WithLongText ... 32 | func WithLongText(longText bool) Option { 33 | return func(o *Options) { 34 | o.longText = longText 35 | } 36 | } 37 | 38 | // WithAppID ... 39 | func WithAppID(appID int64) Option { 40 | return func(o *Options) { 41 | o.appID = appID 42 | } 43 | } 44 | 45 | // WithListener ... 46 | func WithListener(listener tts.SpeechWsv2SynthesisListener) Option { 47 | return func(o *Options) { 48 | o.Listener = listener 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rag/deepdoc/extractor/extractor.go: -------------------------------------------------------------------------------- 1 | package extractor 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var reDocx = regexp.MustCompile(`\.docx$`) 8 | var reXlsx = regexp.MustCompile(`\.xlsx$`) 9 | var rePptx = regexp.MustCompile(`\.pptx$`) 10 | var rePdf = regexp.MustCompile(`\.pdf$`) 11 | var reTxtx = regexp.MustCompile(`\.(txt|py|js|java|c|cpp|h|php|go|ts|sh|cs|kt)$`) 12 | var reDoc = regexp.MustCompile(`\.doc$`) 13 | var reCsv = regexp.MustCompile(`\.csv$`) 14 | var reJson = regexp.MustCompile(`\.json$`) 15 | var reMdx = regexp.MustCompile(`\.md$`) 16 | 17 | var extractors = map[string]Extractor{} 18 | 19 | func init() { 20 | extractors["doc"] = NewDocumentExtractor() 21 | extractors["faq"] = NewFaqExtractor() 22 | extractors["unknown"] = NewUnknownExtractor() 23 | } 24 | 25 | // Extractor ... 26 | type Extractor interface { 27 | Extract(*Meta, []byte) ([]string, error) 28 | } 29 | 30 | // Meta ... 31 | type Meta struct { 32 | Path string 33 | Filename string 34 | } 35 | 36 | // Extract ... 37 | func Extract(meta *Meta, content []byte, typ string) ([]string, error) { 38 | return extractors[typ].Extract(meta, content) 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 shawn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/llm/claude/claude.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/joho/godotenv" 10 | 11 | "github.com/showntop/llmack/llm" 12 | "github.com/showntop/llmack/llm/anthropic" 13 | ) 14 | 15 | func init() { 16 | godotenv.Load() 17 | } 18 | 19 | func main() { 20 | ctx := context.Background() 21 | llm.WithSingleConfig(map[string]any{ 22 | "api_key": os.Getenv("claude_api_key"), 23 | "base_url": "http://v2.open.venus.oa.com/llmproxy", 24 | }) 25 | 26 | resp, err := llm.NewInstance(anthropic.Name).Invoke(ctx, 27 | // []llm.Message{llm.UserPromptMessage("Prove that all entire functions that are also injective take the form f (z) = az + 6 with a, b € C, and a ‡ 0.")}, 28 | []llm.Message{llm.NewUserTextMessage("你好")}, 29 | llm.WithStream(true), 30 | llm.WithModel("claude-3-7-sonnet-20250219")) 31 | if err != nil { 32 | panic(err) 33 | } 34 | // fmt.Println(resp.Result()) 35 | for it := resp.Stream().Take(); it != nil; it = resp.Stream().Take() { 36 | xxx, _ := json.Marshal(it) 37 | fmt.Println(string(xxx)) 38 | // fmt.Println("final: ", it.Delta.Message) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/bookkeeper/api/chat_handler.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/bookkeeper-ai/bookkeeper/services" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | type ChatHandler struct { 11 | chatService *services.ChatService 12 | } 13 | 14 | func NewChatHandler(chatService *services.ChatService) *ChatHandler { 15 | return &ChatHandler{ 16 | chatService: chatService, 17 | } 18 | } 19 | 20 | type ChatRequest struct { 21 | Message string `json:"message" binding:"required"` 22 | Medias []string `json:"medias"` 23 | } 24 | 25 | type ChatResponse struct { 26 | Response string `json:"response"` 27 | } 28 | 29 | func (h *ChatHandler) SendMessage(c *gin.Context) { 30 | var req ChatRequest 31 | if err := c.ShouldBindJSON(&req); err != nil { 32 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 33 | return 34 | } 35 | 36 | response, err := h.chatService.SendMessage(c.Request.Context(), req.Message, req.Medias) 37 | if err != nil { 38 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 39 | return 40 | } 41 | 42 | c.JSON(http.StatusOK, ChatResponse{ 43 | Response: response, 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /llm/config.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | // ConfigSupplier ... 8 | type ConfigSupplier interface { 9 | Get(key string) any 10 | } 11 | 12 | // ConfigSupplierFunc ... 13 | type ConfigSupplierFunc func(key string) any 14 | 15 | // WithConfigSupplier ... 16 | func WithConfigSupplier(c ConfigSupplier) { 17 | Config = c 18 | } 19 | 20 | // Config default cache 21 | var Config ConfigSupplier = &envConfig{} 22 | 23 | type envConfig struct { 24 | Value map[string]any 25 | } 26 | 27 | // Get ... 28 | func (c *envConfig) Get(key string) any { 29 | return os.Getenv(key) 30 | } 31 | 32 | // WithConfigs ... 33 | func WithConfigs(c map[string]any) { 34 | Config = &mapConfig{Value: c} 35 | } 36 | 37 | type mapConfig struct { 38 | Value map[string]any 39 | } 40 | 41 | // Get ... 42 | func (c *mapConfig) Get(key string) any { 43 | return c.Value[key] 44 | } 45 | 46 | // WithSingleConfig ... 47 | func WithSingleConfig(c any) { 48 | Config = &anyConfig{Value: c} 49 | } 50 | 51 | // anyConfig for any type config 52 | type anyConfig struct { 53 | Value any 54 | } 55 | 56 | func (c *anyConfig) Get(key string) any { 57 | return c.Value 58 | } 59 | -------------------------------------------------------------------------------- /tool/factory.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import "sync" 4 | 5 | // 注册表锁 6 | var registerLock sync.RWMutex 7 | 8 | // Repo 工具仓库 9 | type Repo interface { 10 | // FetchTool(context.Context, int64, string) (*Metadata, error) 11 | } 12 | 13 | // Factory 工具工厂 14 | type Factory struct { 15 | repo Repo 16 | } 17 | 18 | // NewFactory ... 19 | func NewFactory(repo Repo) *Factory { 20 | return &Factory{repo: repo} 21 | } 22 | 23 | // Spawn ... 24 | func (f *Factory) Spawn(name string) *Tool { 25 | registerLock.RLock() 26 | defer registerLock.RUnlock() 27 | if t, ok := tools[name]; ok { 28 | return t 29 | } 30 | return nil 31 | } 32 | 33 | var defaultFactory = NewFactory(nil) 34 | 35 | // WithRepo ... 36 | func WithRepo(repo Repo) { 37 | defaultFactory.repo = repo 38 | } 39 | 40 | // Spawn ... 41 | func Spawn(name string) *Tool { 42 | x := defaultFactory.Spawn(name) 43 | if x == nil { 44 | return NilTool 45 | } 46 | return x 47 | } 48 | 49 | // Tools 工具注册表 50 | var tools map[string]*Tool = make(map[string]*Tool) 51 | 52 | // Register 注册工具 53 | func Register(t *Tool) { 54 | registerLock.Lock() 55 | defer registerLock.Unlock() 56 | tools[t.Name] = t 57 | } 58 | -------------------------------------------------------------------------------- /example/bookkeeper/readme: -------------------------------------------------------------------------------- 1 | # AI 记账助手 2 | 3 | 一个基于 AI 的智能记账工具,可以帮助用户通过对话方式记录和管理日常收支。 4 | 5 | ## 功能特点 6 | 7 | - 支持通过对话方式记录收支 8 | - 支持上传消费截图,自动识别金额和类别 9 | - 提供消费分析和预算管理建议 10 | - 支持导出消费明细报表 11 | - 提供投资理财建议 12 | 13 | ## 技术栈 14 | 15 | ### 后端 16 | - Go 17 | - Gin Web 框架 18 | - GORM ORM 框架 19 | - PostgreSQL 数据库 20 | - LLMACK AI 框架 21 | 22 | ### 前端 23 | - React 24 | - TypeScript 25 | - Material-UI 26 | - Vite 27 | 28 | ## 快速开始 29 | 30 | ### 后端设置 31 | 32 | 1. 安装 PostgreSQL 数据库 33 | 2. 创建数据库: 34 | ```sql 35 | CREATE DATABASE bookkeeper; 36 | ``` 37 | 38 | 3. 配置数据库连接: 39 | 编辑 `config/config.yaml` 文件,设置数据库连接信息 40 | 41 | 4. 运行后端服务: 42 | ```bash 43 | cd bookkeeper 44 | go mod tidy 45 | go run main.go 46 | ``` 47 | 48 | ### 前端设置 49 | 50 | 1. 安装依赖: 51 | ```bash 52 | cd bookkeeper-web 53 | npm install 54 | ``` 55 | 56 | 2. 运行开发服务器: 57 | ```bash 58 | npm run dev 59 | ``` 60 | 61 | ## API 文档 62 | 63 | ### 交易记录 API 64 | 65 | - POST /api/transactions - 创建新的交易记录 66 | - GET /api/transactions - 获取交易记录列表 67 | - GET /api/transactions/analysis - 获取交易分析数据 68 | 69 | ## 贡献指南 70 | 71 | 1. Fork 项目 72 | 2. 创建特性分支 73 | 3. 提交更改 74 | 4. 推送到分支 75 | 5. 创建 Pull Request 76 | 77 | ## 许可证 78 | 79 | MIT -------------------------------------------------------------------------------- /storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/showntop/llmack/llm" 8 | ) 9 | 10 | type Journey struct { 11 | SessionID string 12 | StepNo int 13 | Kind string 14 | Duration time.Duration 15 | Messages []llm.Message 16 | Metadata any 17 | } 18 | 19 | type Storage interface { 20 | AddNewJourney(ctx context.Context, journey *Journey) error 21 | SaveSession(ctx context.Context, session *Session) error 22 | FetchSession(ctx context.Context, id string) (*Session, error) 23 | UpdateSession(ctx context.Context, session *Session) error 24 | DeleteSession(ctx context.Context, id string) error 25 | } 26 | 27 | type NoneStorage struct { 28 | } 29 | 30 | func (s *NoneStorage) SaveSession(ctx context.Context, session *Session) error { 31 | return nil 32 | } 33 | 34 | func (s *NoneStorage) FetchSession(ctx context.Context, id string) (*Session, error) { 35 | return nil, nil 36 | } 37 | 38 | func (s *NoneStorage) UpdateSession(ctx context.Context, session *Session) error { 39 | return nil 40 | } 41 | 42 | func (s *NoneStorage) DeleteSession(ctx context.Context, id string) error { 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /prompt/readme: -------------------------------------------------------------------------------- 1 | # Prompt Core 2 | 3 | A Go implementation of prompt optimization framework inspired by Ape, DSPy and PromptWizard. 4 | 5 | ## Key Components 6 | 7 | 1. Prompt Management 8 | - Structured prompt representation 9 | - Template system for dynamic prompts 10 | - Version control for prompts 11 | 12 | 2. Optimization Pipeline 13 | - Mutation strategies 14 | - Evaluation metrics 15 | - Feedback loop 16 | 17 | 3. Training Framework 18 | - Few-shot learning 19 | - Example management 20 | - Performance tracking 21 | 22 | 4. Integration 23 | - LLM API connectors 24 | - Metrics collection 25 | - Result analysis 26 | 27 | ## Basic Usage 28 | 29 | 30 | ## Key Features 31 | 32 | 1. Modular Architecture 33 | - Easily extensible components 34 | - Pluggable optimization strategies 35 | - Custom metric support 36 | 37 | 2. Built-in Methods 38 | - Few-shot optimization 39 | - Prompt mutation 40 | - Example synthesis 41 | - Performance evaluation 42 | 43 | 3. Production Ready 44 | - Concurrent optimization 45 | - Result caching 46 | - Error handling 47 | - Logging 48 | 49 | 4. Developer Experience 50 | - Clear interfaces 51 | - Comprehensive documentation 52 | - Example implementations -------------------------------------------------------------------------------- /agent/response.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/showntop/llmack/llm" 7 | ) 8 | 9 | type TeamRunResponse struct { 10 | sync.RWMutex // for thread safe 11 | 12 | Reasoning string `json:"reasoning"` 13 | Answer string `json:"answer"` 14 | ToolCalls []llm.ToolCall `json:"tool_calls"` 15 | Stream chan *llm.Chunk 16 | MemberResponses []*AgentRunResponse 17 | 18 | Error error 19 | } 20 | 21 | func (t *TeamRunResponse) AddMemberResponse(response *AgentRunResponse) { 22 | t.Lock() 23 | defer t.Unlock() 24 | 25 | if t.MemberResponses == nil { 26 | t.MemberResponses = []*AgentRunResponse{} 27 | } 28 | t.MemberResponses = append(t.MemberResponses, response) 29 | } 30 | 31 | func (t *TeamRunResponse) String() string { 32 | return t.Answer 33 | } 34 | 35 | type AgentRunResponse struct { 36 | Reasoning string `json:"reasoning"` 37 | Answer string `json:"answer"` 38 | ToolCalls []llm.ToolCall `json:"tool_calls"` 39 | Error error 40 | 41 | Stream chan *llm.Chunk 42 | Usage llm.Usage 43 | } 44 | 45 | func (a *AgentRunResponse) Completion() string { 46 | return a.Answer 47 | } 48 | -------------------------------------------------------------------------------- /llm/openai-c/llm.go: -------------------------------------------------------------------------------- 1 | package deepseek 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/showntop/llmack/llm" 9 | ) 10 | 11 | // Name ... 12 | var Name = "openai-c" 13 | 14 | func init() { 15 | llm.Register(Name, func(o *llm.ProviderOptions) llm.Provider { return &LLM{} }) 16 | } 17 | 18 | // LLM ... 19 | type LLM struct { 20 | once sync.Once 21 | engine *llm.OAILLM 22 | } 23 | 24 | // NewLLM ... 25 | func NewLLM() *LLM { 26 | return &LLM{} 27 | } 28 | 29 | // Name ... 30 | func (m *LLM) Name() string { 31 | return Name 32 | } 33 | 34 | // Invoke ... 35 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) { 36 | var err error 37 | m.once.Do(func() { 38 | config, _ := llm.Config.Get(Name).(map[string]any) 39 | if config == nil { 40 | err = fmt.Errorf("openai-c config not found") 41 | } 42 | apiKey, _ := config["api_key"].(string) 43 | baseURL, _ := config["base_url"].(string) 44 | url := baseURL + "/chat/completions" 45 | m.engine = llm.NewOAILLM(url, apiKey) 46 | }) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return m.engine.Invoke(ctx, messages, opts) 51 | } 52 | -------------------------------------------------------------------------------- /rag/deepdoc/chunk/chunk_test.go: -------------------------------------------------------------------------------- 1 | package chunk 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestBase_mergeSplits(t *testing.T) { 9 | type fields struct { 10 | chunkSize int 11 | overlapSize int 12 | } 13 | type args struct { 14 | splits []string 15 | separator string 16 | } 17 | tests := []struct { 18 | name string 19 | fields fields 20 | args args 21 | want []string 22 | }{ 23 | // TODO: Add test cases. 24 | { 25 | name: "test1", 26 | fields: fields{ 27 | chunkSize: 21, 28 | overlapSize: 5, 29 | }, 30 | args: args{ 31 | splits: []string{"1234567890", "1234567890", "1234567890", "1234567890"}, 32 | separator: ",", 33 | }, 34 | want: []string{"1234567890,1234567890", "1234567890,1234567890"}, 35 | }, 36 | } 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | b := &Base{ 40 | chunkSize: tt.fields.chunkSize, 41 | overlapSize: tt.fields.overlapSize, 42 | } 43 | if got := b.mergeSplits(tt.args.splits, tt.args.separator); !reflect.DeepEqual(got, tt.want) { 44 | t.Errorf("Base.mergeSplits() = %v, want %v", got, tt.want) 45 | } 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /speech/tts/aliyun/utils.go: -------------------------------------------------------------------------------- 1 | package aliyun 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | // Header is the basic struct for the TTS header. 10 | type Header struct { 11 | MessageID string `json:"message_id"` 12 | TaskID string `json:"task_id"` 13 | Namespace string `json:"namespace"` 14 | Name string `json:"name"` 15 | Appkey string `json:"appkey"` 16 | } 17 | 18 | func newHeader(taskID, name string) Header { 19 | return Header{ 20 | TaskID: taskID, 21 | Namespace: "FlowingSpeechSynthesizer", 22 | Name: name, 23 | Appkey: "N3CyrsLkHXD2yD3c", 24 | MessageID: Get32UUID(), 25 | } 26 | } 27 | 28 | // Response is the basic struct for the TTS response. 29 | type Response struct { 30 | Header Header `json:"header"` 31 | Payload map[string]interface{} `json:"payload,omitempty"` 32 | } 33 | 34 | // Request is the basic struct for the TTS Request. 35 | type Request struct { 36 | Header Header `json:"header"` 37 | Payload map[string]interface{} `json:"payload,omitempty"` 38 | } 39 | 40 | // Get32UUID ... 41 | func Get32UUID() string { 42 | return strings.ReplaceAll(uuid.NewString(), "-", "") 43 | } 44 | -------------------------------------------------------------------------------- /example/llm/deepseek/deepseek.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/joho/godotenv" 10 | 11 | "github.com/showntop/llmack/llm" 12 | "github.com/showntop/llmack/llm/deepseek" 13 | ) 14 | 15 | func init() { 16 | godotenv.Load() 17 | } 18 | 19 | func main() { 20 | ctx := context.Background() 21 | llm.WithSingleConfig(map[string]any{ 22 | "api_key": os.Getenv("deepseek_api_key"), 23 | // "api_key": os.Getenv("deepseek_api_key2"), 24 | // "base_url": "https://api.lkeap.cloud.tencent.com/v1", 25 | }) 26 | 27 | resp, err := llm.NewInstance(deepseek.Name).Invoke(ctx, 28 | // []llm.Message{llm.UserPromptMessage("Prove that all entire functions that are also injective take the form f (z) = az + 6 with a, b € C, and a ‡ 0.")}, 29 | []llm.Message{llm.NewUserTextMessage("你好")}, 30 | llm.WithStream(true), 31 | llm.WithModel("deepseek-chat")) 32 | if err != nil { 33 | panic(err) 34 | } 35 | // fmt.Println(resp.Result()) 36 | for it := resp.Stream().Take(); it != nil; it = resp.Stream().Take() { 37 | xxx, _ := json.Marshal(it) 38 | fmt.Println(string(xxx)) 39 | // fmt.Println("final: ", it.Delta.Message) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /embedding/string_embedder_test.go: -------------------------------------------------------------------------------- 1 | package embedding 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestStringEmbedder_Embed(t *testing.T) { 10 | type args struct { 11 | ctx context.Context 12 | text string 13 | } 14 | tests := []struct { 15 | name string 16 | s StringEmbedder 17 | args args 18 | want []float32 19 | wantErr bool 20 | }{ 21 | // TODO: Add test cases. 22 | { 23 | name: "test", 24 | s: StringEmbedder{}, 25 | args: args{ 26 | ctx: context.Background(), 27 | text: "hello world", 28 | }, 29 | want: []float32{104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 30 | wantErr: false, 31 | }, 32 | } 33 | for _, tt := range tests { 34 | t.Run(tt.name, func(t *testing.T) { 35 | s := StringEmbedder{} 36 | got, err := s.Embed(tt.args.ctx, tt.args.text) 37 | if (err != nil) != tt.wantErr { 38 | t.Errorf("StringEmbedder.Embed() error = %v, wantErr %v", err, tt.wantErr) 39 | return 40 | } 41 | if !reflect.DeepEqual(got, tt.want) { 42 | t.Errorf("StringEmbedder.Embed() = %v, want %v", got, tt.want) 43 | } 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/agent/browser/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | "github.com/showntop/llmack/agent" 10 | "github.com/showntop/llmack/llm" 11 | "github.com/showntop/llmack/llm/deepseek" 12 | "github.com/showntop/llmack/log" 13 | "github.com/showntop/llmack/pkg/browser" 14 | ) 15 | 16 | func init() { 17 | godotenv.Load() 18 | 19 | log.SetLogger(&log.WrapLogger{}) 20 | 21 | // llm.WithSingleConfig(map[string]any{ 22 | // "api_key": os.Getenv("qwen_api_key"), 23 | // }) 24 | llm.WithSingleConfig(map[string]any{ 25 | "api_key": os.Getenv("deepseek_api_key"), 26 | }) 27 | } 28 | 29 | func main() { 30 | 31 | browserAgent := agent.NewBrowserAgent("browser agent", 32 | // agent.WithModel(llm.NewInstance(qwen.Name, llm.WithDefaultModel("qwen-vl-max-latest"))), 33 | agent.WithModel(llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat"))), 34 | agent.WithBrowserConfig(&browser.BrowserConfig{ 35 | "headless": false, 36 | }), 37 | ) 38 | 39 | response := browserAgent.Invoke(context.Background(), "去美团外卖查找所有烤鸭店的电话号码、名称、地址") 40 | if response.Error != nil { 41 | panic(response.Error) 42 | } 43 | fmt.Println(response.Completion()) 44 | } 45 | -------------------------------------------------------------------------------- /rag/deepdoc/doc.go: -------------------------------------------------------------------------------- 1 | package deepdoc 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | 7 | "baliance.com/gooxml/document" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | // DocDocument ... 12 | type DocDocument struct { 13 | } 14 | 15 | // Doc ... 16 | func Doc() *DocDocument { 17 | return &DocDocument{} 18 | } 19 | 20 | // Extract ... 21 | func (d *DocDocument) Extract(filename string, binary []byte) ([]string, error) { 22 | // save tmp file 23 | f, err := os.CreateTemp("/tmp", uuid.NewString()) 24 | // defer os.Remove(f.Name()) 25 | _, err = f.Write(binary) 26 | // safe 校验 27 | cmd := exec.Command("soffice", "--headless", "--convert-to", "docx", f.Name(), "--outdir", "/tmp") 28 | // cmd.Stdout = os.Stdout 29 | // cmd.Stderr = os.Stderr 30 | if err := cmd.Run(); err != nil { 31 | return nil, err 32 | } 33 | 34 | // doc, err := document.Read(bytes.NewReader(binary), int64(len(binary))) 35 | doc, err := document.Open(f.Name() + ".docx") 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | sections := []string{} 41 | for _, para := range doc.Paragraphs() { 42 | //run为每个段落相同格式的文字组成的片段 43 | for _, run := range para.Runs() { 44 | sections = append(sections, run.Text()) 45 | } 46 | } 47 | return sections, nil 48 | } 49 | -------------------------------------------------------------------------------- /llm/doubao/doubao.go: -------------------------------------------------------------------------------- 1 | package doubao 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/showntop/llmack/llm" 9 | ) 10 | 11 | // Name ... 12 | var Name = "doubao" 13 | 14 | func init() { 15 | llm.Register(Name, NewLLM) 16 | } 17 | 18 | // LLM ... 19 | type LLM struct { 20 | once sync.Once 21 | engine *llm.OAILLM 22 | } 23 | 24 | // NewLLM ... 25 | func NewLLM(o *llm.ProviderOptions) llm.Provider { 26 | return &LLM{} 27 | } 28 | 29 | // Name ... 30 | func (m *LLM) Name() string { 31 | return Name 32 | } 33 | 34 | // Invoke ... 35 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) { 36 | var err error 37 | m.once.Do(func() { 38 | url := "https://ark.cn-beijing.volces.com/api/v3" 39 | config, _ := llm.Config.Get(Name).(map[string]any) 40 | if config == nil { 41 | err = fmt.Errorf("doubao config not found") 42 | } 43 | apiKey, _ := config["api_key"].(string) 44 | baseURL, _ := config["base_url"].(string) 45 | if baseURL != "" { 46 | url = baseURL + "/chat/completions" 47 | } 48 | m.engine = llm.NewOAILLM(url, apiKey) 49 | }) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return m.engine.Invoke(ctx, messages, opts) 54 | } 55 | -------------------------------------------------------------------------------- /llm/anthropic/claude.go: -------------------------------------------------------------------------------- 1 | package anthropic 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/showntop/llmack/llm" 9 | ) 10 | 11 | // Name ... 12 | var Name = "anthropic" 13 | 14 | func init() { 15 | llm.Register(Name, NewLLM) 16 | } 17 | 18 | // LLM ... 19 | type LLM struct { 20 | once sync.Once 21 | engine *llm.OAILLM 22 | } 23 | 24 | // NewLLM ... 25 | func NewLLM(options *llm.ProviderOptions) llm.Provider { 26 | return &LLM{} 27 | } 28 | 29 | // Name ... 30 | func (m *LLM) Name() string { 31 | return Name 32 | } 33 | 34 | // Invoke ... 35 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) { 36 | var err error 37 | m.once.Do(func() { 38 | url := "https://api.anthropic.com/v1/messages" 39 | config, _ := llm.Config.Get(Name).(map[string]any) 40 | if config == nil { 41 | err = fmt.Errorf("claude config not found") 42 | } 43 | apiKey, _ := config["api_key"].(string) 44 | baseURL, _ := config["base_url"].(string) 45 | if baseURL != "" { 46 | url = baseURL + "/chat/completions" 47 | } 48 | m.engine = llm.NewOAILLM(url, apiKey) 49 | }) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return m.engine.Invoke(ctx, messages, opts) 54 | } 55 | -------------------------------------------------------------------------------- /program/prompt.go: -------------------------------------------------------------------------------- 1 | package program 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Field represents an input or output field in a signature 9 | type Field struct { 10 | Name string 11 | Type reflect.Kind 12 | Description string 13 | Marker string 14 | Kind string // input or output 15 | } 16 | 17 | // Promptx defines the instruction and input/output about a program 18 | type Promptx struct { 19 | Name string 20 | Description string 21 | Prompt string 22 | Instruction string 23 | InputFields map[string]*Field 24 | OutputFields map[string]*Field 25 | } 26 | 27 | // ValidateInputs checks if the provided inputs match the signature 28 | func (m *Promptx) ValidateInputs(inputs map[string]any) error { 29 | for name := range m.InputFields { 30 | if _, ok := inputs[name]; !ok { 31 | return fmt.Errorf("missing required input field: %s", name) 32 | } 33 | } 34 | return nil 35 | } 36 | 37 | // ValidateOutputs checks if the outputs match the signature 38 | func (m *Promptx) ValidateOutputs(outputs map[string]any) error { 39 | for name := range m.OutputFields { 40 | if _, ok := outputs[name]; !ok { 41 | return fmt.Errorf("missing required output field: %s", name) 42 | } 43 | } 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /embedding/string_embedder.go: -------------------------------------------------------------------------------- 1 | package embedding 2 | 3 | import "context" 4 | 5 | // StringEmbedder ... 6 | type StringEmbedder struct { 7 | } 8 | 9 | // NewStringEmbedder 创建一个新的StringEmbedder实例 10 | var NewStringEmbedder = func() Embedder { 11 | return StringEmbedder{} 12 | } 13 | 14 | // Embed 将文本转换为固定维度的向量 15 | func (s StringEmbedder) Embed(ctx context.Context, text string) ([]float32, error) { 16 | // 1. 固定向量长度 17 | vectorSize := s.Dimension() 18 | result := make([]float32, vectorSize) 19 | 20 | // 2. 获取字符串的字节 21 | bytes := []byte(text) 22 | 23 | // 3. 使用字节生成向量 24 | for i := range vectorSize { 25 | var sum float32 = 0 26 | // 对每个位置,使用一组字节计算一个float值 27 | for j := i; j < len(bytes); j += vectorSize { 28 | sum += float32(bytes[j%len(bytes)]) 29 | } 30 | result[i] = sum 31 | } 32 | 33 | return result, nil 34 | } 35 | 36 | // BatchEmbed 批量将多个文本转换为向量 37 | func (s StringEmbedder) BatchEmbed(ctx context.Context, texts []string) ([][]float32, error) { 38 | results := make([][]float32, len(texts)) 39 | for i, text := range texts { 40 | embedding, err := s.Embed(ctx, text) 41 | if err != nil { 42 | return nil, err 43 | } 44 | results[i] = embedding 45 | } 46 | return results, nil 47 | } 48 | 49 | // Dimension ... 50 | func (s StringEmbedder) Dimension() int { 51 | return 32 52 | } 53 | -------------------------------------------------------------------------------- /llm/deepseek/llm.go: -------------------------------------------------------------------------------- 1 | package deepseek 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/showntop/llmack/llm" 9 | ) 10 | 11 | // Name ... 12 | var Name = "deepseek" 13 | 14 | func init() { 15 | llm.Register(Name, NewLLM) 16 | } 17 | 18 | // LLM ... 19 | type LLM struct { 20 | once sync.Once 21 | engine *llm.OAILLM 22 | } 23 | 24 | // NewLLM ... 25 | func NewLLM(o *llm.ProviderOptions) llm.Provider { 26 | return &LLM{} 27 | } 28 | 29 | // Name ... 30 | func (m *LLM) Name() string { 31 | return Name 32 | } 33 | 34 | // Invoke ... 35 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) { 36 | var err error 37 | m.once.Do(func() { 38 | url := "https://api.deepseek.com/chat/completions" 39 | config, _ := llm.Config.Get(Name).(map[string]any) 40 | if config == nil { 41 | err = fmt.Errorf("deepseek config not found") 42 | // return nil, fmt.Errorf("deepseek config not found") 43 | } 44 | apiKey, _ := config["api_key"].(string) 45 | baseURL, _ := config["base_url"].(string) 46 | if baseURL != "" { 47 | url = baseURL + "/chat/completions" 48 | } 49 | m.engine = llm.NewOAILLM(url, apiKey) 50 | }) 51 | if err != nil { 52 | return nil, err 53 | } 54 | return m.engine.Invoke(ctx, messages, opts) 55 | } 56 | -------------------------------------------------------------------------------- /llm/cache_query.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // QueryProcessor ... 8 | type QueryProcessor func([]Message) (string, error) 9 | 10 | // LastQueryMessage ... 11 | func LastQueryMessage(messages []Message) (string, error) { 12 | return messages[len(messages)-1].Content(), nil // TODO when with multipart content 13 | } 14 | 15 | // ConcatQueryMessage ... 16 | func ConcatQueryMessage(messages []Message) (string, error) { 17 | builder := strings.Builder{} 18 | for i := 0; i < len(messages); i++ { 19 | builder.WriteString(string(messages[i].Role())) 20 | builder.WriteByte(':') 21 | builder.WriteString(messages[i].Content()) 22 | } 23 | return builder.String(), nil 24 | } 25 | 26 | // CompressQueryMessage ... 27 | func CompressQueryMessage(messages []Message) (string, error) { 28 | builder := strings.Builder{} 29 | for i := 0; i < len(messages); i++ { 30 | builder.WriteString(string(messages[i].Role())) 31 | builder.WriteByte(':') 32 | builder.WriteString(messages[i].Content()) 33 | } 34 | return builder.String(), nil 35 | } 36 | 37 | // SummarizeQueryMessage extracts an intelligent summary from conversation messages 38 | func SummarizeQueryMessage(messages []Message) (string, error) { 39 | // 如果消息为空则返回 40 | if len(messages) == 0 { 41 | return "", nil 42 | } 43 | summary := "" 44 | 45 | return summary, nil 46 | } 47 | -------------------------------------------------------------------------------- /tool/crawl/tool.go: -------------------------------------------------------------------------------- 1 | package crawl 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/showntop/llmack/tool" 9 | ) 10 | 11 | var ( 12 | Jina = "Jina" 13 | Colly = "Colly" 14 | ) 15 | 16 | func init() { 17 | register(Jina) 18 | register(Colly) 19 | } 20 | 21 | func register(name string) { 22 | t := tool.New( 23 | tool.WithName(name), 24 | tool.WithKind("code"), 25 | tool.WithDescription("Used to scrape website urls and extract text content"), 26 | tool.WithParameters(tool.Parameter{ 27 | Name: "link", 28 | Type: tool.String, 29 | Required: true, 30 | LLMDescrition: "Valid website url without any quotes.", 31 | Default: "", 32 | }), 33 | tool.WithFunction(func(ctx context.Context, args string) (string, error) { 34 | var params struct { 35 | Link string `json:"link"` 36 | } 37 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil { 38 | return "", err 39 | } 40 | engine, ok := Crawlers[name] 41 | if !ok { 42 | return "", fmt.Errorf("crawler %s not found", name) 43 | } 44 | result, err := engine.Crawl(ctx, params.Link) 45 | if err != nil { 46 | return "", err 47 | } 48 | bytes, err := json.Marshal(result) 49 | if err != nil { 50 | return "", err 51 | } 52 | return string(bytes), nil 53 | }), 54 | ) 55 | tool.Register(t) 56 | } 57 | -------------------------------------------------------------------------------- /example/bookkeeper/api/analysis.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/bookkeeper-ai/bookkeeper/services" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | type AnalysisHandler struct { 12 | analysisService *services.AnalysisService 13 | } 14 | 15 | func NewAnalysisHandler(analysisService *services.AnalysisService) *AnalysisHandler { 16 | return &AnalysisHandler{ 17 | analysisService: analysisService, 18 | } 19 | } 20 | 21 | // GetMonthlyAnalysis 获取月度消费分析 22 | func (h *AnalysisHandler) GetMonthlyAnalysis(c *gin.Context) { 23 | userID := uint(1) // TODO: 从认证中获取用户ID 24 | year, _ := strconv.Atoi(c.DefaultQuery("year", "2024")) 25 | month, _ := strconv.Atoi(c.DefaultQuery("month", "1")) 26 | 27 | analysis, err := h.analysisService.GetMonthlyAnalysis(userID, year, month) 28 | if err != nil { 29 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 30 | return 31 | } 32 | 33 | c.JSON(http.StatusOK, analysis) 34 | } 35 | 36 | // GetBudgetRecommendation 获取预算建议 37 | func (h *AnalysisHandler) GetBudgetRecommendation(c *gin.Context) { 38 | userID := uint(1) // TODO: 从认证中获取用户ID 39 | 40 | recommendation, err := h.analysisService.GetBudgetRecommendation(userID) 41 | if err != nil { 42 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 43 | return 44 | } 45 | 46 | c.JSON(http.StatusOK, recommendation) 47 | } 48 | -------------------------------------------------------------------------------- /example/llm/asure-openai/openai.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | 10 | "github.com/showntop/llmack/llm" 11 | openaic "github.com/showntop/llmack/llm/openai-c" 12 | "github.com/showntop/llmack/log" 13 | ) 14 | 15 | func init() { 16 | godotenv.Load() 17 | } 18 | 19 | func main() { 20 | runWithCache() 21 | } 22 | 23 | func runWithCache() { 24 | ctx := context.Background() 25 | 26 | llm.WithSingleConfig(map[string]any{ 27 | "base_url": os.Getenv("hunyuan_base_url"), 28 | "api_key": os.Getenv("hunyuan_api_key"), 29 | }) 30 | 31 | instance := llm.NewInstance(openaic.Name, 32 | llm.WithCache(llm.NewMemoCache()), 33 | llm.WithLogger(&log.WrapLogger{}), 34 | ) 35 | 36 | resp, err := instance.Invoke(ctx, 37 | []llm.Message{llm.NewUserTextMessage("你好")}, 38 | llm.WithModel("hunyuan"), 39 | llm.WithStream(true), 40 | ) 41 | if err != nil { 42 | panic(err) 43 | } 44 | fmt.Println(resp.Result()) 45 | 46 | resp, err = instance.Invoke(ctx, 47 | []llm.Message{llm.NewUserTextMessage("你好")}, 48 | llm.WithModel("hunyuan"), 49 | llm.WithStream(true), 50 | ) 51 | if err != nil { 52 | panic(err) 53 | } 54 | // stream := resp.Stream() 55 | // for v := stream.Next(); v != nil; v = stream.Next() { 56 | // fmt.Println(string(v.Delta.Message.Content().Data)) 57 | // } 58 | fmt.Println(resp.Result()) 59 | } 60 | -------------------------------------------------------------------------------- /example/llm/openai-c/openai-c.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | 10 | "github.com/showntop/llmack/llm" 11 | openaic "github.com/showntop/llmack/llm/openai-c" 12 | "github.com/showntop/llmack/log" 13 | ) 14 | 15 | func init() { 16 | godotenv.Load() 17 | } 18 | 19 | func main() { 20 | runWithCache() 21 | } 22 | 23 | func runWithCache() { 24 | ctx := context.Background() 25 | 26 | llm.WithSingleConfig(map[string]any{ 27 | "base_url": os.Getenv("hunyuan_base_url"), 28 | "api_key": os.Getenv("hunyuan_api_key"), 29 | }) 30 | 31 | instance := llm.NewInstance(openaic.Name, 32 | llm.WithCache(llm.NewMemoCache()), 33 | llm.WithLogger(&log.WrapLogger{}), 34 | ) 35 | 36 | resp, err := instance.Invoke(ctx, 37 | []llm.Message{llm.NewUserTextMessage("你好")}, 38 | llm.WithModel("hunyuan"), 39 | llm.WithStream(true), 40 | ) 41 | if err != nil { 42 | panic(err) 43 | } 44 | fmt.Println(resp.Result()) 45 | 46 | resp, err = instance.Invoke(ctx, 47 | []llm.Message{llm.NewUserTextMessage("你好")}, 48 | llm.WithModel("hunyuan"), 49 | llm.WithStream(true), 50 | ) 51 | if err != nil { 52 | panic(err) 53 | } 54 | // stream := resp.Stream() 55 | // for v := stream.Next(); v != nil; v = stream.Next() { 56 | // fmt.Println(string(v.Delta.Message.Content().Data)) 57 | // } 58 | fmt.Println(resp.Result()) 59 | } 60 | -------------------------------------------------------------------------------- /tool/crawl/jina.go: -------------------------------------------------------------------------------- 1 | package crawl 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | func init() { 13 | Register(Jina, NewJinaCrawler()) 14 | } 15 | 16 | // JinaCrawler 使用jina搜索 17 | type JinaCrawler struct { 18 | } 19 | 20 | // NewJinaCrawler 创建jina爬虫 21 | func NewJinaCrawler() Crawler { 22 | return &JinaCrawler{} 23 | } 24 | 25 | // Crawl 爬取 26 | func (c *JinaCrawler) Crawl(ctx context.Context, link string) (*Result, error) { 27 | accessURL := fmt.Sprintf("https://r.jina.ai/%s", link) 28 | 29 | req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, accessURL, strings.NewReader("")) 30 | if err != nil { 31 | return nil, err 32 | } 33 | req.Header.Set("Accept", "application/json") 34 | resp, err := http.DefaultClient.Do(req) 35 | if err != nil { 36 | return nil, err 37 | } 38 | defer resp.Body.Close() 39 | 40 | if resp.StatusCode != 200 { 41 | return nil, fmt.Errorf("status code error: %d %s", resp.StatusCode, resp.Status) 42 | } 43 | raw, err := io.ReadAll(resp.Body) 44 | if err != nil { 45 | return nil, err 46 | } 47 | var result struct { 48 | Code int `json:"code"` 49 | Status int `json:"status"` 50 | Data Result `json:"data"` 51 | } 52 | 53 | if err := json.Unmarshal(raw, &result); err != nil { 54 | return nil, err 55 | } 56 | 57 | return &result.Data, nil 58 | } 59 | -------------------------------------------------------------------------------- /tool/file/write_file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "os" 7 | 8 | "github.com/showntop/llmack/tool" 9 | ) 10 | 11 | const WriteFile = "WriteFile" 12 | 13 | func init() { 14 | t := tool.New( 15 | tool.WithName(WriteFile), 16 | tool.WithKind("code"), 17 | tool.WithDescription("Writes text to a file"), 18 | tool.WithParameters( 19 | tool.Parameter{ 20 | Name: "file_name", 21 | LLMDescrition: "Name of the file to write. Only include the file name. Don't include path.", 22 | Type: tool.String, 23 | Required: true, 24 | }, 25 | tool.Parameter{ 26 | Name: "content", 27 | LLMDescrition: "File content to write.", 28 | Type: tool.String, 29 | Required: true, 30 | }, 31 | ), 32 | tool.WithFunction(func(ctx context.Context, args string) (string, error) { 33 | var params struct { 34 | FileName string `json:"file_name"` 35 | Content string `json:"content"` 36 | } 37 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil { 38 | return "", err 39 | } 40 | ff, err := os.Create(params.FileName) 41 | if err != nil { 42 | return "", err 43 | } 44 | defer ff.Close() 45 | _, err = ff.WriteString(params.Content) 46 | if err != nil { 47 | return "", err 48 | } 49 | return "文件写入成功", nil 50 | }), 51 | ) 52 | tool.Register(t) 53 | } 54 | -------------------------------------------------------------------------------- /example/agent/memory/agent.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | "github.com/showntop/llmack/agent" 10 | "github.com/showntop/llmack/llm" 11 | "github.com/showntop/llmack/llm/deepseek" 12 | "github.com/showntop/llmack/log" 13 | "github.com/showntop/llmack/memory" 14 | ) 15 | 16 | var ( 17 | model = llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat")) 18 | ) 19 | 20 | func init() { 21 | godotenv.Load() 22 | 23 | log.SetLogger(&log.WrapLogger{}) 24 | llm.WithSingleConfig(map[string]any{ 25 | "api_key": os.Getenv("deepseek_api_key"), 26 | }) 27 | } 28 | 29 | func main() { 30 | sessionID := "session_1" 31 | 32 | agentx := agent.NewAgent("", 33 | agent.WithMemory(memory.NewFlatMemory(nil)), 34 | agent.WithModel(model), 35 | ) 36 | 37 | response := agentx.Invoke(context.Background(), 38 | "My name is John Doe and I like to hike in the mountains on weekends.", 39 | agent.WithStream(false), 40 | agent.WithSessionID(sessionID), 41 | ) 42 | if response.Error != nil { 43 | panic(response.Error) 44 | } 45 | fmt.Println(response.Answer) 46 | 47 | response2 := agentx.Invoke(context.Background(), 48 | "What are my hobbies?", 49 | agent.WithStream(false), 50 | agent.WithSessionID(sessionID), 51 | ) 52 | if response2.Error != nil { 53 | panic(response2.Error) 54 | } 55 | fmt.Println(response2.Answer) 56 | } 57 | -------------------------------------------------------------------------------- /example/bookkeeper/web/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /workflow/definition.go: -------------------------------------------------------------------------------- 1 | package workflow 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Link 链接节点 8 | func (w *Workflow) LinkWithCondition(node1, expr string, node2 any, nodes ...Node) *Workflow { 9 | var targetID string 10 | if node, ok := node2.(Node); ok { 11 | targetID = node.ID 12 | w.AddNode(node) 13 | } else { 14 | targetID = node2.(string) 15 | } 16 | edges := make([]Edge, 0) 17 | edges = append(edges, Edge{ 18 | ID: fmt.Sprintf("%s-%s", node1, targetID), 19 | Source: node1, 20 | Target: targetID, 21 | Express: expr, 22 | }) 23 | w.Edges = append(w.Edges, edges...) 24 | 25 | if node, ok := node2.(Node); ok && len(nodes) > 0 { 26 | nodes = append([]Node{node}, nodes...) 27 | w.Link(nodes...) 28 | } 29 | return w 30 | } 31 | 32 | // Link 链接节点 33 | func (w *Workflow) Link(nodes ...Node) *Workflow { 34 | w.AddNode(nodes...) 35 | 36 | edges := make([]Edge, 0) 37 | for i := 0; i < len(nodes)-1; i++ { 38 | edges = append(edges, Edge{ 39 | ID: fmt.Sprintf("%s-%s", nodes[i].ID, nodes[i+1].ID), 40 | Source: nodes[i].ID, 41 | Target: nodes[i+1].ID, 42 | }) 43 | } 44 | w.Edges = append(w.Edges, edges...) 45 | return w 46 | } 47 | 48 | // AddNode 添加节点 49 | func (w *Workflow) AddNode(nodes ...Node) *Workflow { 50 | w.Nodes = append(w.Nodes, nodes...) 51 | return w 52 | } 53 | 54 | // NewWorkflow 初始化workflow 55 | func NewWorkflow(id int64, name string) *Workflow { 56 | return &Workflow{ 57 | ID: id, 58 | Name: name, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /example/bookkeeper/web/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rag/indexer.go: -------------------------------------------------------------------------------- 1 | package rag 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/showntop/llmack/log" 7 | "github.com/showntop/llmack/vdb" 8 | ) 9 | 10 | // Indexer ... 11 | type Indexer struct { 12 | vdb vdb.VDB 13 | scalarDB ScalarDB // object 14 | } 15 | 16 | // NewIndexer ... 17 | func NewIndexer(name string, config any) (*Indexer, error) { 18 | vdb, err := vdb.NewVDB(name, config) // new vdb 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | return &Indexer{vdb: vdb}, nil 24 | } 25 | 26 | // Retrieve TODO: 实现检索逻辑 27 | func (r *Indexer) Retrieve(ctx context.Context, query string, opts ...SearchOption) ([]*vdb.Document, error) { 28 | searchOpts := &SearchOptions{} 29 | for _, opt := range opts { 30 | opt(searchOpts) 31 | } 32 | log.InfoContextf(ctx, "Retrieve knowledge: %v query: %s options: %+v", searchOpts.LibraryID, query, searchOpts) 33 | 34 | vdbSearchOpts := &vdb.SearchOptions{ 35 | TopK: searchOpts.TopK, 36 | Threshold: searchOpts.ScoreThreshold, 37 | } 38 | docs, err := r.vdb.SearchQueryWithOptions(ctx, query, vdbSearchOpts) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return docs, nil 44 | } 45 | 46 | func (r *Indexer) Index(ctx context.Context, docs []*vdb.Document, opts *SearchOptions) ([]*vdb.Document, error) { 47 | log.InfoContextf(ctx, "Index documents %+v with options %+v", docs, opts) 48 | // 将知识库中的数据转换为向量 49 | if err := r.vdb.Store(ctx, docs...); err != nil { 50 | return nil, err 51 | } 52 | 53 | return nil, nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/browser/dom/process_dom_test.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "os" 7 | "testing" 8 | "time" 9 | 10 | "github.com/playwright-community/playwright-go" 11 | ) 12 | 13 | func TestProcessDOM(t *testing.T) { 14 | pw, err := playwright.Run() 15 | if err != nil { 16 | t.Fatalf("could not start playwright: %v", err) 17 | } 18 | browser, err := pw.Chromium.Launch() 19 | if err != nil { 20 | t.Fatalf("could not launch browser: %v", err) 21 | } 22 | page, err := browser.NewPage() 23 | if err != nil { 24 | t.Fatalf("could not create page: %v", err) 25 | } 26 | if _, err = page.Goto("https://kayak.com/flights"); err != nil { 27 | t.Fatalf("could not goto: %v", err) 28 | } 29 | 30 | jsCode, err := os.ReadFile("./buildDomTree.js") 31 | if err != nil { 32 | t.Fatalf("failed to read JS file: %v", err) 33 | } 34 | 35 | start := time.Now() 36 | domTree, err := page.Evaluate(string(jsCode)) 37 | if err != nil { 38 | log.Fatalf("failed to evaluate JS: %v", err) 39 | } 40 | elapsed := time.Since(start) 41 | t.Logf("Time: %.2fs\n", elapsed.Seconds()) 42 | 43 | if err := os.MkdirAll("./tmp", 0755); err != nil { 44 | log.Fatalf("failed to create tmp dir: %v", err) 45 | } 46 | domTreeJSON, err := json.MarshalIndent(domTree, "", " ") 47 | if err != nil { 48 | log.Fatalf("failed to marshal domTree: %v", err) 49 | } 50 | if err := os.WriteFile("./tmp/dom.json", domTreeJSON, 0644); err != nil { 51 | log.Fatalf("failed to write dom.json: %v", err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tool/browser/dom/process_dom_test.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "os" 7 | "testing" 8 | "time" 9 | 10 | "github.com/playwright-community/playwright-go" 11 | ) 12 | 13 | func TestProcessDOM(t *testing.T) { 14 | pw, err := playwright.Run() 15 | if err != nil { 16 | t.Fatalf("could not start playwright: %v", err) 17 | } 18 | browser, err := pw.Chromium.Launch() 19 | if err != nil { 20 | t.Fatalf("could not launch browser: %v", err) 21 | } 22 | page, err := browser.NewPage() 23 | if err != nil { 24 | t.Fatalf("could not create page: %v", err) 25 | } 26 | if _, err = page.Goto("https://kayak.com/flights"); err != nil { 27 | t.Fatalf("could not goto: %v", err) 28 | } 29 | 30 | jsCode, err := os.ReadFile("./buildDomTree.js") 31 | if err != nil { 32 | t.Fatalf("failed to read JS file: %v", err) 33 | } 34 | 35 | start := time.Now() 36 | domTree, err := page.Evaluate(string(jsCode)) 37 | if err != nil { 38 | log.Fatalf("failed to evaluate JS: %v", err) 39 | } 40 | elapsed := time.Since(start) 41 | t.Logf("Time: %.2fs\n", elapsed.Seconds()) 42 | 43 | if err := os.MkdirAll("./tmp", 0755); err != nil { 44 | log.Fatalf("failed to create tmp dir: %v", err) 45 | } 46 | domTreeJSON, err := json.MarshalIndent(domTree, "", " ") 47 | if err != nil { 48 | log.Fatalf("failed to marshal domTree: %v", err) 49 | } 50 | if err := os.WriteFile("./tmp/dom.json", domTreeJSON, 0644); err != nil { 51 | log.Fatalf("failed to write dom.json: %v", err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/bookkeeper/services/chat_service.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/showntop/llmack/llm" 8 | "github.com/showntop/llmack/llm/qwen" 9 | 10 | "github.com/bookkeeper-ai/bookkeeper/config" 11 | ) 12 | 13 | type ChatService struct { 14 | llmClient *llm.Instance 15 | } 16 | 17 | func NewChatService(cfg *config.Config) (*ChatService, error) { 18 | // 初始化 LLM 客户端 19 | // llmClient := llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat")) 20 | llmClient := llm.NewInstance(qwen.Name, llm.WithDefaultModel("qwen-vl-max")) 21 | 22 | return &ChatService{ 23 | llmClient: llmClient, 24 | }, nil 25 | } 26 | 27 | func (s *ChatService) SendMessage(ctx context.Context, message string, images []string) (string, error) { 28 | multipartContents := make([]*llm.MultipartContent, 0) 29 | multipartContents = append(multipartContents, llm.MultipartContentText(message)) 30 | for _, image := range images { 31 | multipartContents = append(multipartContents, llm.MultipartContentImageURL(image)) 32 | } 33 | 34 | // 构建消息 35 | messages := []llm.Message{ 36 | llm.NewSystemMessage("你是一个智能记账助手,根据用户的输入识别其中的内容,并根据内容生成记账凭证。"), 37 | llm.NewUserMultipartMessage( 38 | multipartContents..., 39 | ), 40 | } 41 | 42 | // 调用 LLM 43 | response, err := s.llmClient.Invoke(ctx, messages, llm.WithStream(true)) 44 | if err != nil { 45 | log.Printf("Error invoking LLM: %v", err) 46 | return "", err 47 | } 48 | return response.Result().Message.Content(), nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/browser/utils.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | import ( 4 | "errors" 5 | "runtime" 6 | 7 | "github.com/kbinani/screenshot" 8 | ) 9 | 10 | // getScreenResolution returns the width and height of the main (first) display 11 | func getScreenResolution() map[string]int { 12 | n := screenshot.NumActiveDisplays() 13 | if n == 0 { 14 | return map[string]int{"width": 1920, "height": 1080} // fallback 15 | } 16 | b := screenshot.GetDisplayBounds(0) 17 | return map[string]int{ 18 | "width": b.Dx(), 19 | "height": b.Dy(), 20 | } 21 | } 22 | 23 | // getWindowAdjustments returns (borderAdjust, titlebarAdjust) for major OSes 24 | func getWindowAdjustments() (int, int) { 25 | os := runtime.GOOS 26 | switch os { 27 | case "darwin": 28 | return -4, 24 // macOS: small title bar, no border 29 | case "win32": 30 | return -8, 0 // Windows: border on the left 31 | default: 32 | return 0, 0 // Linux or others 33 | } 34 | } 35 | 36 | func ParseNumberToInt(value any) (int, error) { 37 | if value == nil { 38 | return 0, nil 39 | } 40 | if v, ok := value.(int); ok { 41 | return v, nil 42 | } 43 | if v, ok := value.(float64); ok { 44 | return int(v), nil 45 | } 46 | return 0, errors.New("value is not a number") 47 | } 48 | 49 | func ParseNumberToFloat(value any) (float64, error) { 50 | if value == nil { 51 | return 0, nil 52 | } 53 | if v, ok := value.(float64); ok { 54 | return v, nil 55 | } 56 | if v, ok := value.(int); ok { 57 | return float64(v), nil 58 | } 59 | return 0, errors.New("value is not a number") 60 | } 61 | -------------------------------------------------------------------------------- /embedding/openai_embedder.go: -------------------------------------------------------------------------------- 1 | package embedding 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/sashabaranov/go-openai" 7 | ) 8 | 9 | // OpenAIEmbedder OpenAI embedder 10 | type OpenAIEmbedder struct { 11 | client *openai.Client 12 | model openai.EmbeddingModel 13 | } 14 | 15 | // NewOpenAIEmbedder 创建一个新的OpenAI embedder实例 16 | func NewOpenAIEmbedder(apiKey string, model openai.EmbeddingModel) *OpenAIEmbedder { 17 | if model == "" { 18 | model = openai.AdaEmbeddingV2 // 默认使用 text-embedding-ada-002 19 | } 20 | 21 | return &OpenAIEmbedder{ 22 | client: openai.NewClient(apiKey), 23 | model: model, 24 | } 25 | } 26 | 27 | // Embed 将文本转换为向量 28 | func (e *OpenAIEmbedder) Embed(ctx context.Context, text string) ([]float32, error) { 29 | resp, err := e.client.CreateEmbeddings(ctx, openai.EmbeddingRequest{ 30 | Input: []string{text}, 31 | Model: e.model, 32 | }) 33 | 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | if len(resp.Data) == 0 { 39 | return []float32{}, nil 40 | } 41 | 42 | return resp.Data[0].Embedding, nil 43 | } 44 | 45 | // BatchEmbed 批量将多个文本转换为向量 46 | func (e *OpenAIEmbedder) BatchEmbed(ctx context.Context, texts []string) ([][]float32, error) { 47 | resp, err := e.client.CreateEmbeddings(ctx, openai.EmbeddingRequest{ 48 | Input: texts, 49 | Model: e.model, 50 | }) 51 | 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | embeddings := make([][]float32, len(resp.Data)) 57 | for i, data := range resp.Data { 58 | embeddings[i] = data.Embedding 59 | } 60 | 61 | return embeddings, nil 62 | } 63 | -------------------------------------------------------------------------------- /agent/memory.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | type TeamMemory struct { 9 | sync.RWMutex 10 | 11 | SharedContexts SharedContexts 12 | } 13 | 14 | type SharedContexts struct { 15 | Context string 16 | TeamMemberInteractions []TeamMemberInteraction 17 | } 18 | 19 | type TeamMemberInteraction struct { 20 | MemberName string 21 | Task string 22 | Response *AgentRunResponse 23 | } 24 | 25 | func (t *TeamMemory) SetSharedContext(ctx context.Context, text string) (string, error) { 26 | t.Lock() 27 | defer t.Unlock() 28 | 29 | t.SharedContexts.Context = text 30 | return t.SharedContexts.Context, nil 31 | } 32 | 33 | func (t *TeamMemory) GetSharedContext(ctx context.Context) (string, error) { 34 | t.RLock() 35 | defer t.RUnlock() 36 | 37 | return t.SharedContexts.Context, nil 38 | } 39 | 40 | func (t *TeamMemory) GetTeamMemberInteractions(ctx context.Context) ([]TeamMemberInteraction, error) { 41 | t.RLock() 42 | defer t.RUnlock() 43 | 44 | return t.SharedContexts.TeamMemberInteractions, nil 45 | } 46 | 47 | func (t *TeamMemory) AddTeamMemberInteractions(ctx context.Context, memberName string, task string, response *AgentRunResponse) error { 48 | t.Lock() 49 | defer t.Unlock() 50 | 51 | t.SharedContexts.TeamMemberInteractions = append(t.SharedContexts.TeamMemberInteractions, struct { 52 | MemberName string 53 | Task string 54 | Response *AgentRunResponse 55 | }{ 56 | MemberName: memberName, 57 | Task: task, 58 | Response: response, 59 | }) 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /llm/cache.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | 8 | "github.com/showntop/llmack/embedding" 9 | "github.com/showntop/llmack/vdb" 10 | ) 11 | 12 | // CachedDocument ... 13 | type CachedDocument struct { 14 | ID string 15 | Query string 16 | Answer string 17 | Score float64 18 | Vector []float32 19 | } 20 | 21 | // CacheFactory ... 22 | type CacheFactory func() Cache 23 | 24 | // Cache ... 25 | type Cache interface { 26 | Fetch(context.Context, []Message) (*CachedDocument, bool, error) 27 | Store(context.Context, *CachedDocument, string) error 28 | } 29 | 30 | // BaseCache ... 31 | type BaseCache struct { 32 | Config *CacheConfig 33 | Embedder embedding.Embedder 34 | VectorStore vdb.VDB 35 | Ranker Scorer 36 | } 37 | 38 | // SetQueryProcessor ... 39 | func (c *BaseCache) SetQueryProcessor(p QueryProcessor) { 40 | c.Config.QueryProcessor = p 41 | } 42 | 43 | // CacheConfig 定义缓存的配置选项 44 | type CacheConfig struct { 45 | TTL time.Duration 46 | MaxEntries int 47 | CleanupInterval time.Duration 48 | EnableMetrics bool 49 | QueryProcessor QueryProcessor 50 | } 51 | 52 | // DefaultConfig 返回默认配置 53 | func DefaultConfig() *CacheConfig { 54 | return &CacheConfig{ 55 | TTL: 5 * time.Minute, 56 | MaxEntries: 1000, 57 | CleanupInterval: 1 * time.Minute, 58 | EnableMetrics: true, 59 | QueryProcessor: LastQueryMessage, 60 | } 61 | } 62 | 63 | // 添加自定义错误类型 64 | var ( 65 | ErrCacheMiss = errors.New("cache miss") 66 | ErrCacheFull = errors.New("cache is full") 67 | ) 68 | -------------------------------------------------------------------------------- /tool/entity.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "github.com/getkin/kin-openapi/openapi3" 5 | ) 6 | 7 | // ParameterType 枚举类型 8 | type ParameterType string 9 | 10 | const ( 11 | Array ParameterType = "array" 12 | String ParameterType = "string" 13 | Number ParameterType = "number" 14 | Boolean ParameterType = "boolean" 15 | Select ParameterType = "select" 16 | SecretInput ParameterType = "secret-input" 17 | File ParameterType = "file" 18 | ) 19 | 20 | // Parameter 用于描述API参数的结构体 21 | type Parameter struct { 22 | Name string 23 | Label string 24 | HumanDescription string 25 | Placeholder string 26 | Type ParameterType 27 | LLMDescrition string 28 | Required bool 29 | Default any 30 | Min float64 31 | Max float64 32 | Options []string 33 | } 34 | 35 | type ParamsOneOf struct { 36 | params1 []Parameter 37 | params2 *openapi3.Schema 38 | } 39 | 40 | func (p *ParamsOneOf) Parameters() any { 41 | if p == nil { 42 | return nil 43 | } 44 | if len(p.params1) > 0 { 45 | properties := map[string]any{} 46 | required := []string{} 47 | for _, p := range p.params1 { 48 | properties[p.Name] = map[string]any{ 49 | "description": p.LLMDescrition, 50 | "type": p.Type, 51 | } 52 | if p.Required { 53 | required = append(required, p.Name) 54 | } 55 | } 56 | return map[string]any{ 57 | "type": "object", 58 | "properties": properties, 59 | "required": required, 60 | } 61 | } 62 | return p.params2 63 | } 64 | -------------------------------------------------------------------------------- /example/llm/qwen/qwen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | 10 | "github.com/showntop/llmack/llm" 11 | "github.com/showntop/llmack/llm/qwen" 12 | "github.com/showntop/llmack/log" 13 | ) 14 | 15 | func init() { 16 | godotenv.Load() 17 | } 18 | 19 | func main() { 20 | runWithCache() 21 | } 22 | 23 | func runWithCache() { 24 | ctx := context.Background() 25 | 26 | llm.WithSingleConfig(map[string]any{ 27 | "api_key": os.Getenv("qwen_api_key"), 28 | }) 29 | 30 | instance := llm.NewInstance(qwen.Name, 31 | llm.WithCache(llm.NewMemoCache()), 32 | llm.WithLogger(&log.WrapLogger{}), 33 | ) 34 | 35 | resp, err := instance.Invoke(ctx, 36 | []llm.Message{ 37 | llm.NewUserTextMessage("你好"), 38 | llm.NewUserMultipartMessage( 39 | llm.MultipartContentImageURL("https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"), 40 | llm.MultipartContentText("这是一张关于猫的照片吗"), 41 | ), 42 | }, 43 | llm.WithModel("qwen-vl-max-latest"), 44 | llm.WithStream(true), 45 | ) 46 | if err != nil { 47 | panic(err) 48 | } 49 | fmt.Println(resp.Result()) 50 | 51 | resp, err = instance.Invoke(ctx, 52 | []llm.Message{llm.NewUserTextMessage("你好")}, 53 | llm.WithModel("qwen-vl-max-latest"), 54 | llm.WithStream(true), 55 | ) 56 | if err != nil { 57 | panic(err) 58 | } 59 | // stream := resp.Stream() 60 | // for v := stream.Next(); v != nil; v = stream.Next() { 61 | // fmt.Println(string(v.Delta.Message.Content().Data)) 62 | // } 63 | fmt.Println(resp.Result()) 64 | } 65 | -------------------------------------------------------------------------------- /speech/tts/aliyun/handler.go: -------------------------------------------------------------------------------- 1 | package aliyun 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/showntop/llmack/speech" 8 | ) 9 | 10 | // SpeechWsSynthesisListener is the listener of 11 | type SpeechWsSynthesisListener interface { 12 | OnSynthesizeStarted(*speech.TTSResult) 13 | OnSentenceEnded(*speech.TTSResult) 14 | OnSynthesized(*speech.TTSResult) 15 | OnSentenceStarted(*speech.TTSResult) 16 | OnSynthesizeCompleted(*speech.TTSResult, error) 17 | OnSynthesizeFailed(*speech.TTSResult, error) 18 | OnAudioResult([]byte) 19 | } 20 | 21 | type listener struct { 22 | ff *os.File 23 | t *TTS 24 | } 25 | 26 | func newListener(t *TTS) *listener { 27 | f, _ := os.Create("tts.wav") 28 | return &listener{ 29 | ff: f, 30 | t: t, 31 | } 32 | } 33 | 34 | func (l *listener) OnAudioResult(data []byte) { 35 | l.ff.Write(data) 36 | l.t.result <- data 37 | } 38 | func (l *listener) OnSentenceEnded(r *speech.TTSResult) { 39 | fmt.Printf("tts sentence ended, result:%+v\n", r) 40 | } 41 | func (l *listener) OnSentenceStarted(r *speech.TTSResult) { 42 | fmt.Printf("tts sentence started, result:%+v\n", r) 43 | } 44 | func (l *listener) OnSynthesizeCompleted(_ *speech.TTSResult, _ error) { 45 | fmt.Printf("tts synthesize completed\n") 46 | } 47 | func (l *listener) OnSynthesizeStarted(_ *speech.TTSResult) { 48 | fmt.Println("tts synthesize started") 49 | } 50 | func (l *listener) OnSynthesized(_ *speech.TTSResult) { 51 | fmt.Println("tts sentence synthesized 有新的合成结果返回。") 52 | } 53 | func (l *listener) OnSynthesizeFailed(_ *speech.TTSResult, _ error) { 54 | fmt.Println("tts OnSynthesizeFailed") 55 | } 56 | -------------------------------------------------------------------------------- /embedding/onnx_embedder.go: -------------------------------------------------------------------------------- 1 | package embedding 2 | 3 | // import ( 4 | // "fmt" 5 | 6 | // "github.com/owulveryck/onnx-go" 7 | // "github.com/owulveryck/onnx-go/backend/x/gorgonnx" 8 | // ) 9 | 10 | // // OnnxEmbedder 实现基于ONNX的文本嵌入 11 | // type OnnxEmbedder struct { 12 | // model *onnx.Model 13 | // dim int 14 | // } 15 | 16 | // // NewOnnxEmbedder 创建新的ONNX嵌入器 17 | // func NewOnnxEmbedder(modelPath string, dimension int) (*OnnxEmbedder, error) { 18 | // backend := gorgonnx.NewGraph() 19 | // model := onnx.NewModel(backend) 20 | 21 | // // 加载模型 22 | // if err := model.Load(modelPath); err != nil { 23 | // return nil, fmt.Errorf("failed to load model: %v", err) 24 | // } 25 | 26 | // return &OnnxEmbedder{ 27 | // model: model, 28 | // dim: dimension, 29 | // }, nil 30 | // } 31 | 32 | // // Embed 生成文本的向量表示 33 | // func (e *OnnxEmbedder) Embed(text string) ([]float64, error) { 34 | // // 这里需要根据具体的模型实现文本预处理和推理 35 | // // 示例实现: 36 | // input := preprocessText(text) 37 | // output, err := e.model.Predict(input) 38 | // if err != nil { 39 | // return nil, fmt.Errorf("prediction failed: %v", err) 40 | // } 41 | 42 | // return normalizeOutput(output), nil 43 | // } 44 | 45 | // // Dimension 返回嵌入向量的维度 46 | // func (e *OnnxEmbedder) Dimension() int { 47 | // return e.dim 48 | // } 49 | 50 | // // 文本预处理函数 51 | // func preprocessText(text string) []float32 { 52 | // // 实现文本预处理逻辑 53 | // // 例如:分词、编码等 54 | // return nil 55 | // } 56 | 57 | // // 输出归一化 58 | // func normalizeOutput(output interface{}) []float64 { 59 | // // 实现输出处理逻辑 60 | // // 将模型输出转换为标准化的float64切片 61 | // return nil 62 | // } 63 | -------------------------------------------------------------------------------- /tool/mobile/controller/action.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/getkin/kin-openapi/openapi3gen" 9 | "github.com/showntop/llmack/pkg/structx" 10 | "github.com/showntop/llmack/tool" 11 | ) 12 | 13 | type ActionFunc[T, D any] func(ctx context.Context, input T) (output D, err error) 14 | 15 | type Action struct { 16 | Name string 17 | Tool *tool.Tool 18 | } 19 | 20 | func NewAction[T, D any](name string, description string, actionFunc ActionFunc[T, D]) (*Action, error) { 21 | fun := func(ctx context.Context, args string) (string, error) { 22 | var inst T 23 | inst = structx.NewInstance[T]() 24 | 25 | if err := json.Unmarshal([]byte(args), &inst); err != nil { 26 | return "", fmt.Errorf("failed to unmarshal arguments in json, %v", err) 27 | } 28 | 29 | resp, err := actionFunc(ctx, inst) 30 | if err != nil { 31 | return "", fmt.Errorf("failed to execute action, %v", err) 32 | } 33 | 34 | output, err := json.Marshal(resp) 35 | if err != nil { 36 | return "", fmt.Errorf("failed to marshal output in json, %v", err) 37 | } 38 | 39 | return string(output), nil 40 | } 41 | 42 | schemaCustomizer := tool.DefaultSchemaCustomizer 43 | 44 | sc, err := openapi3gen.NewSchemaRefForValue(structx.NewInstance[T](), nil, openapi3gen.SchemaCustomizer(schemaCustomizer)) 45 | if err != nil { 46 | return nil, fmt.Errorf("new SchemaRef from T failed: %w", err) 47 | } 48 | 49 | tool := tool.New( 50 | tool.WithName(name), 51 | tool.WithDescription(description), 52 | tool.WithParameters(sc.Value), 53 | tool.WithFunction(fun), 54 | ) 55 | 56 | return &Action{ 57 | Name: name, 58 | Tool: tool, 59 | }, nil 60 | } 61 | -------------------------------------------------------------------------------- /tool/think/think.go: -------------------------------------------------------------------------------- 1 | package think 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | "github.com/showntop/llmack/tool" 8 | ) 9 | 10 | const Think = "ThinkTool" 11 | 12 | func init() { 13 | t := tool.New( 14 | tool.WithName(Think), 15 | tool.WithKind("code"), 16 | tool.WithDescription("Intelligent problem-solving assistant that comprehends tasks, identifies key variables, and makes efficient decisions, all while providing detailed, self-driven reasoning for its choices. Do not assume anything, take the details from given data only."), 17 | tool.WithParameters(tool.Parameter{ 18 | Name: "task", 19 | Type: tool.String, 20 | Required: true, 21 | LLMDescrition: "Task description which needs reasoning.", 22 | Default: "", 23 | }), 24 | tool.WithFunction(func(ctx context.Context, args string) (string, error) { 25 | var params struct { 26 | Task string `json:"task"` 27 | } 28 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil { 29 | return "", err 30 | } 31 | // TODO: Implement actual thinking logic 32 | return "任务分析:" + params.Task + "。基于现有信息进行推理分析...", nil 33 | }), 34 | ) 35 | tool.Register(t) 36 | } 37 | 38 | var prompt = ` 39 | Given the following overall objective 40 | Objective: 41 | {goals} 42 | 43 | and the following task, {{task_description}}. 44 | 45 | Below is last tool response: 46 | {{last_tool_response}} 47 | 48 | Below is the relevant tool response: 49 | {{relevant_tool_response}} 50 | 51 | Perform the task by understanding the problem, extracting variables, and being smart 52 | and efficient. Provide a descriptive response, make decisions yourself when 53 | confronted with choices and provide reasoning for ideas / decisions. 54 | ` 55 | -------------------------------------------------------------------------------- /workflow/node/tool.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/showntop/llmack/tool" 9 | "github.com/showntop/llmack/workflow" 10 | wf "github.com/showntop/llmack/workflow" 11 | ) 12 | 13 | type toolNode struct { 14 | executeable 15 | Identifier 16 | Node *wf.Node 17 | } 18 | 19 | // ToolNode initializes new function node with function definition and configured arguments and results 20 | func ToolNode(node *workflow.Node) (*toolNode, error) { 21 | // if def.Kind != wf.NodeKindFunction.Name() { 22 | // return nil, fmt.Errorf("expecting function kind") 23 | // } 24 | 25 | return &toolNode{Node: node}, nil 26 | } 27 | 28 | // Exec executes function node 29 | // 30 | // Configured arguments are evaluated with the node's scope and input and passed 31 | // to the function 32 | // 33 | // Configured results are evaluated with the results from the function and final scope is then returned 34 | func (n *toolNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) { 35 | toolName, _ := n.Node.Metadata["tool_name"].(string) 36 | stream, _ := n.Node.Metadata["stream"].(bool) 37 | 38 | // if providerKind == "" || toolName == "" { 39 | if toolName == "" { 40 | return nil, fmt.Errorf("provider_kind or tool_name is empty") 41 | } 42 | 43 | toolRun := tool.Spawn(toolName) 44 | 45 | inputsJSON, err := json.Marshal(r.Inputs) 46 | if err != nil { 47 | return nil, fmt.Errorf("failed to marshal inputs: %v", err) 48 | } 49 | 50 | if stream { 51 | result, err := toolRun.Invoke(ctx, string(inputsJSON)) 52 | return map[string]any{"result": result}, err 53 | } 54 | result, err := toolRun.Invoke(ctx, string(inputsJSON)) 55 | return map[string]any{"result": result}, err 56 | } 57 | -------------------------------------------------------------------------------- /llm/mock.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | var MockLLMModelName = "mock" 10 | 11 | type MockLLMModel struct { 12 | } 13 | 14 | func (m *MockLLMModel) Invoke(ctx context.Context, messages []Message, opts *InvokeOptions) (*Response, error) { 15 | if opts.Stream { 16 | response := NewStreamResponse() 17 | go func() { 18 | response.stream.Push(NewChunk(0, NewAssistantMessage("我是"), &Usage{ 19 | PromptTokens: 1, 20 | CompletionTokens: 1, 21 | })) 22 | response.stream.Push(NewChunk(0, NewAssistantMessage("mock的"), &Usage{ 23 | PromptTokens: 1, 24 | CompletionTokens: 1, 25 | })) 26 | response.stream.Push(NewChunk(0, NewAssistantMessage("内容"), &Usage{ 27 | PromptTokens: 1, 28 | CompletionTokens: 1, 29 | })) 30 | response.stream.Push(NewChunk(0, NewAssistantMessage("结束"), &Usage{ 31 | PromptTokens: 1, 32 | CompletionTokens: 1, 33 | })) 34 | if len(opts.Tools) > 0 { 35 | toolCalls := []*ToolCall{} 36 | for _, tool := range opts.Tools { 37 | toolCalls = append(toolCalls, &ToolCall{ 38 | ID: uuid.New().String(), 39 | Function: ToolCallFunction{ 40 | Name: tool.Function.Name, 41 | }, 42 | }) 43 | } 44 | response.stream.Push(NewChunk(0, NewAssistantMessage("").WithToolCalls(toolCalls), &Usage{ 45 | PromptTokens: 1, 46 | CompletionTokens: 1, 47 | })) 48 | } 49 | 50 | }() 51 | return response, nil 52 | } else { 53 | return &Response{}, nil 54 | } 55 | } 56 | 57 | func (m *MockLLMModel) Name() string { 58 | return MockLLMModelName 59 | } 60 | 61 | func init() { 62 | Register(MockLLMModelName, func(o *ProviderOptions) Provider { return &MockLLMModel{} }) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/structx/generic.go: -------------------------------------------------------------------------------- 1 | package structx 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // NewInstance create an instance of the given type T. 8 | // the main purpose of this function is to create an instance of a type, can handle the type of T is a pointer or not. 9 | // eg. NewInstance[int] returns 0. 10 | // eg. NewInstance[*int] returns *0 (will be ptr of 0, not nil!). 11 | func NewInstance[T any]() T { 12 | typ := TypeOf[T]() 13 | 14 | switch typ.Kind() { 15 | case reflect.Map: 16 | return reflect.MakeMap(typ).Interface().(T) 17 | case reflect.Slice, reflect.Array: 18 | return reflect.MakeSlice(typ, 0, 0).Interface().(T) 19 | case reflect.Ptr: 20 | typ = typ.Elem() 21 | origin := reflect.New(typ) 22 | inst := origin 23 | 24 | for typ.Kind() == reflect.Ptr { 25 | typ = typ.Elem() 26 | inst = inst.Elem() 27 | inst.Set(reflect.New(typ)) 28 | } 29 | 30 | return origin.Interface().(T) 31 | default: 32 | var t T 33 | return t 34 | } 35 | } 36 | 37 | // TypeOf returns the type of T. 38 | // eg. TypeOf[int] returns reflect.TypeOf(int). 39 | // eg. TypeOf[*int] returns reflect.TypeOf(*int). 40 | func TypeOf[T any]() reflect.Type { 41 | return reflect.TypeOf((*T)(nil)).Elem() 42 | } 43 | 44 | // PtrOf returns a pointer of T. 45 | // useful when you want to get a pointer of a value, in some config, for example. 46 | // eg. PtrOf[int] returns *int. 47 | // eg. PtrOf[*int] returns **int. 48 | func PtrOf[T any](v T) *T { 49 | return &v 50 | } 51 | 52 | type Pair[F, S any] struct { 53 | First F 54 | Second S 55 | } 56 | 57 | // Reverse returns a new slice with elements in reversed order. 58 | func Reverse[S ~[]E, E any](s S) S { 59 | d := make(S, len(s)) 60 | for i := 0; i < len(s); i++ { 61 | d[i] = s[len(s)-i-1] 62 | } 63 | 64 | return d 65 | } 66 | -------------------------------------------------------------------------------- /vision/siliconflow/vision.go: -------------------------------------------------------------------------------- 1 | package siliconflow 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "io" 8 | "net/http" 9 | 10 | "github.com/showntop/llmack/vision" 11 | ) 12 | 13 | // Name ... 14 | var Name = "siliconflow" 15 | 16 | func init() { 17 | vision.Register(Name, &Vision{}) 18 | } 19 | 20 | type Vision struct { 21 | } 22 | 23 | func (v *Vision) GenerateImage(ctx context.Context, prompt string, optFuncs ...vision.InvokeOption) (string, error) { 24 | options := vision.InvokeOptions{} 25 | for _, optFunc := range optFuncs { 26 | optFunc(&options) 27 | } 28 | 29 | url := "https://api.siliconflow.cn/v1/images/generations" 30 | 31 | params := map[string]any{ 32 | "prompt": prompt, 33 | "model": "black-forest-labs/FLUX.1-schnell", 34 | "seed": 4999999999, 35 | } 36 | payload, _ := json.Marshal(params) 37 | req, err := http.NewRequest("POST", url, bytes.NewReader(payload)) 38 | if err != nil { 39 | return "null", err 40 | } 41 | req.Header.Add("Authorization", "Bearer "+options.ApiKey) 42 | req.Header.Add("Content-Type", "application/json") 43 | 44 | resp, err := http.DefaultClient.Do(req) 45 | if err != nil { 46 | return "null", err 47 | } 48 | defer resp.Body.Close() 49 | body, err := io.ReadAll(resp.Body) 50 | if err != nil { 51 | return "null", err 52 | } 53 | var result Result 54 | json.Unmarshal(body, &result) 55 | 56 | return result.Images[0].Url, nil 57 | } 58 | 59 | type Result struct { 60 | Images []struct { 61 | Url string `json:"url"` 62 | } `json:"images"` 63 | Timings struct { 64 | Inference float64 `json:"inference"` 65 | } `json:"timings"` 66 | Seed int `json:"seed"` 67 | SharedId string `json:"shared_id"` 68 | Data []struct { 69 | Url string `json:"url"` 70 | } `json:"data"` 71 | Created int `json:"created"` 72 | } 73 | -------------------------------------------------------------------------------- /vdb/vdb.go: -------------------------------------------------------------------------------- 1 | package vdb 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/pgvector/pgvector-go" 9 | ) 10 | 11 | var constructors = map[string]func(config any) (VDB, error){} 12 | 13 | // NewVDB ... 14 | func NewVDB(name string, config any) (VDB, error) { 15 | constructor, ok := constructors[name] 16 | if !ok { 17 | return nil, fmt.Errorf("vdb %s not found", name) 18 | } 19 | vdb, err := constructor(config) 20 | if err != nil { 21 | return nil, fmt.Errorf("new vdb %s failed: %w", name, err) 22 | } 23 | return vdb, nil 24 | } 25 | 26 | // Register ... 27 | func Register(name string, constructor func(config any) (VDB, error)) { 28 | constructors[name] = constructor 29 | } 30 | 31 | // VDB ... 32 | type VDB interface { 33 | Create(context.Context) error 34 | Store(context.Context, ...*Document) error 35 | // BatchStore(context.Context, []string, [][]float64) error 36 | 37 | SearchWithOptions(context.Context, []float32, *SearchOptions) ([]*Document, error) 38 | Search(context.Context, []float32, ...SearchOption) ([]*Document, error) 39 | SearchQuery(context.Context, string, ...SearchOption) ([]*Document, error) 40 | SearchQueryWithOptions(context.Context, string, *SearchOptions) ([]*Document, error) 41 | 42 | Delete(context.Context, string) error 43 | Close() error 44 | } 45 | 46 | // Document 文档 47 | type Document struct { 48 | ID string `json:"id"` 49 | Title string `json:"title"` 50 | Content string `json:"content"` 51 | ContentHash string `json:"content_hash"` 52 | Embedding pgvector.Vector `json:"embedding"` 53 | Similarity float32 `json:"similarity"` 54 | Metadata map[string]any `json:"metadata"` 55 | CreatedAt time.Time `json:"created_at"` 56 | UpdatedAt time.Time `json:"updated_at"` 57 | } 58 | -------------------------------------------------------------------------------- /vision/hook.go: -------------------------------------------------------------------------------- 1 | package vision 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "database/sql/driver" 7 | "io" 8 | 9 | "go.opentelemetry.io/otel" 10 | "go.opentelemetry.io/otel/attribute" 11 | "go.opentelemetry.io/otel/codes" 12 | "go.opentelemetry.io/otel/trace" 13 | ) 14 | 15 | // Hook ... 16 | type Hook interface { 17 | OnBeforeInvoke(context.Context) context.Context 18 | OnAfterInvoke(ctx context.Context, err error) 19 | OnFirstChunk(context.Context, error) context.Context 20 | } 21 | 22 | // OtelHook ... 23 | type OtelHook struct { 24 | provider trace.TracerProvider 25 | tracer trace.Tracer 26 | } 27 | 28 | // HookOption ... 29 | type HookOption func(*OtelHook) 30 | 31 | // NewOtelHook ... 32 | func NewOtelHook(opts ...HookOption) Hook { 33 | h := &OtelHook{} 34 | h.provider = otel.GetTracerProvider() 35 | h.tracer = h.provider.Tracer("github.com/showntop/llmack/opentelemetry") 36 | 37 | return h 38 | } 39 | 40 | // OnFirstChunk ... 41 | func (h *OtelHook) OnFirstChunk(ctx context.Context, _ error) context.Context { 42 | return ctx 43 | } 44 | 45 | // OnBeforeInvoke ... 46 | func (h *OtelHook) OnBeforeInvoke(ctx context.Context) context.Context { 47 | ctx, _ = h.tracer.Start(ctx, "llm/invoke", trace.WithSpanKind(trace.SpanKindInternal)) 48 | return ctx 49 | } 50 | 51 | // OnAfterInvoke ... 52 | func (h *OtelHook) OnAfterInvoke(ctx context.Context, err error) { 53 | span := trace.SpanFromContext(ctx) 54 | if !span.IsRecording() { 55 | return 56 | } 57 | defer span.End() 58 | 59 | attrs := make([]attribute.KeyValue, 0, 4) 60 | 61 | span.SetAttributes(attrs...) 62 | switch err { 63 | case nil, 64 | driver.ErrSkip, 65 | io.EOF, // end of rows iterator 66 | sql.ErrNoRows: 67 | // ignore 68 | default: 69 | span.RecordError(err) 70 | span.SetStatus(codes.Error, err.Error()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /storage/postgres_store.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "gorm.io/driver/postgres" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type PostgresStorage struct { 12 | db *gorm.DB 13 | } 14 | 15 | func SetupPostgresStorage(dns string) error { 16 | db, err := gorm.Open(postgres.Open(dns), &gorm.Config{}) 17 | if err != nil { 18 | return err 19 | } 20 | db.AutoMigrate(&Session{}) 21 | return nil 22 | } 23 | 24 | func NewPostgresStorage(db *sql.DB) Storage { 25 | gormDB, err := gorm.Open(postgres.New(postgres.Config{Conn: db}), &gorm.Config{}) 26 | if err != nil { 27 | panic(err) 28 | } 29 | return &PostgresStorage{ 30 | db: gormDB, 31 | } 32 | } 33 | 34 | func NewPostgresStorageWithDNS(dns string) Storage { 35 | gormDB, err := gorm.Open(postgres.Open(dns), &gorm.Config{}) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return &PostgresStorage{ 40 | db: gormDB, 41 | } 42 | } 43 | 44 | func NewPostgresStorageWithGormDB(gormDB *gorm.DB) Storage { 45 | return &PostgresStorage{ 46 | db: gormDB, 47 | } 48 | } 49 | 50 | func (s *PostgresStorage) AddNewJourney(ctx context.Context, journey *Journey) error { 51 | return s.db.Create(journey).Error 52 | } 53 | 54 | func (s *PostgresStorage) SaveSession(ctx context.Context, session *Session) error { 55 | return s.db.Create(session).Error 56 | } 57 | 58 | func (s *PostgresStorage) FetchSession(ctx context.Context, id string) (*Session, error) { 59 | var session Session 60 | if err := s.db.Where("id = ?", id).First(&session).Error; err != nil { 61 | return nil, err 62 | } 63 | return &session, nil 64 | } 65 | 66 | func (s *PostgresStorage) UpdateSession(ctx context.Context, session *Session) error { 67 | return s.db.Save(session).Error 68 | } 69 | 70 | func (s *PostgresStorage) DeleteSession(ctx context.Context, id string) error { 71 | return s.db.Delete(&Session{}, "id = ?", id).Error 72 | } 73 | -------------------------------------------------------------------------------- /tool/browser/controller/utils.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "reflect" 7 | 8 | "github.com/invopop/jsonschema" 9 | "github.com/xeipuuv/gojsonschema" 10 | ) 11 | 12 | func removeRefRecursive(v interface{}) interface{} { 13 | switch vv := v.(type) { 14 | case map[string]interface{}: 15 | delete(vv, "$ref") 16 | for k, val := range vv { 17 | vv[k] = removeRefRecursive(val) 18 | } 19 | return vv 20 | case []interface{}: 21 | for i, val := range vv { 22 | vv[i] = removeRefRecursive(val) 23 | } 24 | return vv 25 | default: 26 | return v 27 | } 28 | } 29 | 30 | func GenerateSchema(typeDefinition interface{}) string { 31 | var modelName string 32 | if reflect.TypeOf(typeDefinition).Kind() == reflect.Ptr { 33 | modelName = reflect.TypeOf(typeDefinition).Elem().Name() 34 | } else { 35 | modelName = reflect.TypeOf(typeDefinition).Name() 36 | } 37 | s := jsonschema.Reflect(typeDefinition) 38 | s.Definitions[modelName].Title = modelName 39 | b, err := json.Marshal(s.Definitions[modelName]) 40 | if err != nil { 41 | panic(err) 42 | } 43 | var m map[string]interface{} 44 | if err := json.Unmarshal(b, &m); err != nil { 45 | panic(err) 46 | } 47 | // remove all $ref 48 | m = removeRefRecursive(m).(map[string]interface{}) 49 | b2, err := json.MarshalIndent(m, "", " ") 50 | if err != nil { 51 | panic(err) 52 | } 53 | return string(b2) 54 | } 55 | 56 | func ValidateSchema(schemaString string, data map[string]interface{}) error { 57 | schemaLoader := gojsonschema.NewStringLoader(schemaString) 58 | schema, err := gojsonschema.NewSchema(schemaLoader) 59 | if err != nil { 60 | return err 61 | } 62 | validResult, err := schema.Validate(gojsonschema.NewGoLoader(data)) 63 | if err != nil { 64 | return err 65 | } 66 | if validResult.Valid() { 67 | return nil 68 | } 69 | return errors.New("invalid schema") 70 | } 71 | -------------------------------------------------------------------------------- /speech/tts.go: -------------------------------------------------------------------------------- 1 | package speech 2 | 3 | import "context" 4 | 5 | // StreamTTS ... 6 | type StreamTTS interface { 7 | Prepare() error 8 | Input(string) error 9 | Complete() error 10 | Terminate() error 11 | StreamResult() chan []byte 12 | } 13 | 14 | // TextTTS is the struct for text to speech 15 | type TextTTS interface { 16 | Synthesize(ctx context.Context, text string) (*TextTTSResult, error) 17 | } 18 | 19 | // RealtimeTTS is the struct for text to speech 20 | type RealtimeTTS interface { 21 | Synthesize(ctx context.Context, text string) (*TTSResult, error) 22 | } 23 | 24 | // TTSResult is the basic struct for the TTS response. 25 | type TTSResult struct { 26 | SessionID string `json:"session_id"` //音频流唯一 id,由客户端在握手阶段生成并赋值在调用参数中 27 | // RequestId string `json:"request_id"` //音频流唯一 id,由服务端在握手阶段自动生成 28 | MessageID string `json:"message_id"` //本 message 唯一 id 29 | Data chan []byte `json:"data"` //最新语音合成文本结果/音频流数据 30 | Audios []byte 31 | Subtitles []Subtitle `json:"subtitles"` 32 | } 33 | 34 | func NewTTSResult() *TTSResult { 35 | ttr := &TTSResult{Data: make(chan []byte, 100)} 36 | return ttr 37 | } 38 | 39 | type TextTTSResult struct { 40 | Audio string 41 | Subtitles []Subtitle 42 | } 43 | 44 | type Subtitle struct { 45 | // ⽂本信息。 46 | Text string `json:"text,omitnil,omitempty" name:"text"` 47 | 48 | // ⽂本对应tts语⾳开始时间戳,单位ms。 49 | BeginTime int64 `json:"BeginTime,omitnil,omitempty" name:"BeginTime"` 50 | 51 | // ⽂本对应tts语⾳结束时间戳,单位ms。 52 | EndTime int64 `json:"EndTime,omitnil,omitempty" name:"EndTime"` 53 | 54 | // 该文本在时间戳数组中的开始位置,从0开始。 55 | BeginIndex int64 `json:"BeginIndex,omitnil,omitempty" name:"BeginIndex"` 56 | 57 | // 该文本在时间戳数组中的结束位置,从0开始。 58 | EndIndex int64 `json:"EndIndex,omitnil,omitempty" name:"EndIndex"` 59 | 60 | // 该字的音素。 61 | // 注意:此字段可能返回 null,表示取不到有效值。 62 | Phoneme string `json:"Phoneme,omitnil,omitempty" name:"Phoneme"` 63 | } 64 | -------------------------------------------------------------------------------- /llm/ollama/llm.go: -------------------------------------------------------------------------------- 1 | package ollama 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net/http" 7 | "net/url" 8 | 9 | "github.com/ollama/ollama/api" 10 | "github.com/showntop/llmack/llm" 11 | ) 12 | 13 | const ( 14 | // Name name of llm 15 | Name = "ollama" 16 | ) 17 | 18 | func init() { 19 | llm.Register(Name, func(o *llm.ProviderOptions) llm.Provider { return &LLM{} }) 20 | } 21 | 22 | // LLM ... 23 | type LLM struct { 24 | model string 25 | client *api.Client 26 | name string 27 | } 28 | 29 | // Invoke ... 30 | func (m *LLM) Invoke(ctx context.Context, messages []llm.Message, opts *llm.InvokeOptions) (*llm.Response, error) { 31 | if len(messages) == 0 { 32 | return nil, errors.New("empty messages") 33 | } 34 | if err := m.setupClient(); err != nil { 35 | return nil, err 36 | } 37 | 38 | response := llm.NewStreamResponse() 39 | req := &api.ChatRequest{ 40 | Model: opts.Model, 41 | Stream: &opts.Stream, 42 | Tools: nil, 43 | } 44 | for _, m := range messages { 45 | req.Messages = append(req.Messages, api.Message{ 46 | Role: string(m.Role()), 47 | Content: m.Content(), 48 | }) 49 | } 50 | // TODO tools support 51 | 52 | idx := 1 53 | err := m.client.Chat(ctx, req, func(resp api.ChatResponse) error { 54 | response.Stream().Push(llm.NewChunk(idx, llm.NewAssistantMessage(resp.Message.Content), nil)) 55 | if resp.Done { 56 | response.Stream().Close() 57 | return nil 58 | } 59 | idx++ 60 | return nil 61 | }) 62 | 63 | if err != nil { 64 | return nil, err 65 | } 66 | return response, nil 67 | } 68 | 69 | func (m *LLM) setupClient() error { 70 | config, _ := llm.Config.Get(Name).(map[string]any) 71 | if config == nil { 72 | return errors.New("ollama config not found") 73 | } 74 | urlx, err := url.Parse(config["base_url"].(string)) 75 | if err != nil { 76 | return err 77 | } 78 | client := api.NewClient( 79 | urlx, 80 | http.DefaultClient, 81 | ) 82 | m.client = client 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /example/rag/pgvector/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/joho/godotenv" 11 | "github.com/showntop/llmack/embedding" 12 | "github.com/showntop/llmack/vdb" 13 | "github.com/showntop/llmack/vdb/pgvector" 14 | ) 15 | 16 | func main() { 17 | godotenv.Load() 18 | 19 | vvv, err := pgvector.New(&pgvector.Config{ 20 | DNS: os.Getenv("pgvector_dns"), 21 | Embedder: embedding.NewStringEmbedder(), 22 | Table: "knowledges", 23 | }) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | // err = vvv.Create(context.Background()) 29 | // if err != nil { 30 | // panic(err) 31 | // } 32 | 33 | // err = vvv.Store(context.Background(), mockDocuments()...) 34 | // if err != nil { 35 | // panic(err) 36 | // } 37 | 38 | docs, err := vvv.SearchQuery(context.Background(), "埃菲尔铁塔", vdb.WithTopk(2)) 39 | if err != nil { 40 | panic(err) 41 | } 42 | for _, doc := range docs { 43 | fmt.Println(doc.ID, doc.Title, doc.Content, doc.Embedding) 44 | } 45 | } 46 | 47 | func mockDocuments() []*vdb.Document { 48 | contents := ` 49 | 1. 埃菲尔铁塔:位于法国巴黎,是世界上最著名的地标之一,由古斯塔夫·埃菲尔设计,建于1889年。 50 | 2. 长城:位于中国,是世界七大奇迹之一,始建于秦朝至明朝,全长超过20000公里。 51 | 3. 大峡谷国家公园:位于美国亚利桑那州,以其深深的峡谷和壮丽的景色而闻名,它被科罗拉多河切割而成。 52 | 4. 罗马斗兽场:位于意大利罗马,建于公元70-80年间,是古罗马帝国最大的圆形竞技场。 53 | 5. 泰姬陵:位于印度阿格拉,由莫卧儿皇帝沙贾汗于1653年为纪念他的妻子而建成,是世界新七大奇迹之一。 54 | 6. 悉尼歌剧院:位于澳大利亚悉尼港,是20世纪最具标志性的建筑之一,以其独特的帆船设计而闻名。 55 | 7. 卢浮宫博物馆:位于法国巴黎,是世界上最大的博物馆之一,收藏丰富,包括达·芬奇的《蒙娜丽莎》和希腊的《米洛的维纳斯》。 56 | 8. 尼亚加拉大瀑布:位于美国和加拿大边境,由三个主要瀑布组成,其壮观的景色每年吸引数百万游客。 57 | 9. 圣索菲亚大教堂:位于土耳其伊斯坦布尔,最初建于公元537年,曾是东正教大教堂和清真寺,现为博物馆。 58 | 10. 马丘比丘:位于秘鲁安第斯山脉高原上的古印加遗址,世界新七大奇迹之一,海拔2400多米。 59 | ` 60 | 61 | var docs []*vdb.Document 62 | for idx, str := range strings.Split(contents, "\n") { 63 | if str == "" { 64 | continue 65 | } 66 | docs = append(docs, &vdb.Document{ 67 | ID: strconv.FormatInt(int64(idx+1), 10) + "X", 68 | Content: str, 69 | }) 70 | } 71 | return docs 72 | } 73 | -------------------------------------------------------------------------------- /pkg/browser/dom/utils_test.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConvertSimpleXpathToCssSelector(t *testing.T) { 8 | // Test empty xpath returns empty string 9 | if got := ConvertSimpleXpathToCssSelector(""); got != "" { 10 | t.Errorf("Expected empty string, got %q", got) 11 | } 12 | 13 | // Test a simple xpath without indices 14 | xpath := "/html/body/div/span" 15 | expected := "html > body > div > span" 16 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected { 17 | t.Errorf("Expected %q, got %q", expected, got) 18 | } 19 | 20 | // Test xpath with an index on one element: [2] should translate to :nth-of-type(2) 21 | xpath = "/html/body/div[2]/span" 22 | expected = "html > body > div:nth-of-type(2) > span" 23 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected { 24 | t.Errorf("Expected %q, got %q", expected, got) 25 | } 26 | 27 | // Test xpath with indices on multiple elements 28 | xpath = "/ul/li[3]/a[1]" 29 | expected = "ul > li:nth-of-type(3) > a:nth-of-type(1)" 30 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected { 31 | t.Errorf("Expected %q, got %q", expected, got) 32 | } 33 | } 34 | 35 | func TestEnhancedCssSelectorForElement(t *testing.T) { 36 | dummyElement := &DOMElementNode{ 37 | TagName: "div", 38 | IsVisible: true, 39 | Parent: nil, 40 | Xpath: "/html/body/div[2]", 41 | Attributes: map[string]string{ 42 | "class": "foo bar", 43 | "id": "my-id", 44 | "placeholder": `some "quoted" text`, 45 | "data-testid": "123", 46 | }, 47 | Children: []DOMBaseNode{}, 48 | } 49 | 50 | actualSelector := EnhancedCssSelectorForElement(dummyElement, true) 51 | expectedSelector := `html > body > div:nth-of-type(2).foo.bar[data-testid="123"][id="my-id"][placeholder*="some \"quoted\" text"]` 52 | 53 | if actualSelector != expectedSelector { 54 | t.Errorf("Expected %s, but got %s", expectedSelector, actualSelector) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tool/browser/dom/utils_test.go: -------------------------------------------------------------------------------- 1 | package dom 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConvertSimpleXpathToCssSelector(t *testing.T) { 8 | // Test empty xpath returns empty string 9 | if got := ConvertSimpleXpathToCssSelector(""); got != "" { 10 | t.Errorf("Expected empty string, got %q", got) 11 | } 12 | 13 | // Test a simple xpath without indices 14 | xpath := "/html/body/div/span" 15 | expected := "html > body > div > span" 16 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected { 17 | t.Errorf("Expected %q, got %q", expected, got) 18 | } 19 | 20 | // Test xpath with an index on one element: [2] should translate to :nth-of-type(2) 21 | xpath = "/html/body/div[2]/span" 22 | expected = "html > body > div:nth-of-type(2) > span" 23 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected { 24 | t.Errorf("Expected %q, got %q", expected, got) 25 | } 26 | 27 | // Test xpath with indices on multiple elements 28 | xpath = "/ul/li[3]/a[1]" 29 | expected = "ul > li:nth-of-type(3) > a:nth-of-type(1)" 30 | if got := ConvertSimpleXpathToCssSelector(xpath); got != expected { 31 | t.Errorf("Expected %q, got %q", expected, got) 32 | } 33 | } 34 | 35 | func TestEnhancedCssSelectorForElement(t *testing.T) { 36 | dummyElement := &DOMElementNode{ 37 | TagName: "div", 38 | IsVisible: true, 39 | Parent: nil, 40 | Xpath: "/html/body/div[2]", 41 | Attributes: map[string]string{ 42 | "class": "foo bar", 43 | "id": "my-id", 44 | "placeholder": `some "quoted" text`, 45 | "data-testid": "123", 46 | }, 47 | Children: []DOMBaseNode{}, 48 | } 49 | 50 | actualSelector := EnhancedCssSelectorForElement(dummyElement, true) 51 | expectedSelector := `html > body > div:nth-of-type(2).foo.bar[data-testid="123"][id="my-id"][placeholder*="some \"quoted\" text"]` 52 | 53 | if actualSelector != expectedSelector { 54 | t.Errorf("Expected %s, but got %s", expectedSelector, actualSelector) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/storage/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | 10 | "github.com/showntop/llmack/agent" 11 | "github.com/showntop/llmack/llm" 12 | "github.com/showntop/llmack/llm/deepseek" 13 | "github.com/showntop/llmack/log" 14 | "github.com/showntop/llmack/storage" 15 | ) 16 | 17 | var model = llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat")) 18 | 19 | func init() { 20 | godotenv.Load() 21 | 22 | log.SetLogger(&log.WrapLogger{}) 23 | llm.WithSingleConfig(map[string]any{ 24 | "api_key": os.Getenv("deepseek_api_key"), 25 | }) 26 | 27 | if err := storage.SetupPostgresStorage(os.Getenv("postgres_dns")); err != nil { 28 | panic(err) 29 | } 30 | } 31 | 32 | func main() { 33 | 34 | stockSearcher := agent.NewAgent( 35 | "Stock Searcher", 36 | agent.WithModel(model), 37 | agent.WithRole("Searches the web for information on a stock."), 38 | agent.WithStorage(storage.NewPostgresStorageWithDNS(os.Getenv("postgres_dns"))), 39 | ) 40 | 41 | webSearcher := agent.NewAgent( 42 | "Web Searcher", 43 | agent.WithModel(model), 44 | agent.WithRole("Searches the web for information on a company."), 45 | agent.WithStorage(storage.NewJSONStorage("tmp/web_searcher")), 46 | ) 47 | 48 | team := agent.NewTeam( 49 | agent.TeamModeCoordinate, 50 | agent.WithName("Stock Team"), 51 | agent.WithModel(model), 52 | agent.WithInstructions( 53 | "You can search the stock market for information about a particular company's stock.", 54 | "You can also search the web for wider company information.", 55 | ), 56 | agent.WithMembers(stockSearcher, webSearcher), 57 | agent.WithStorage(storage.NewJSONStorage("tmp/stock_team")), 58 | ) 59 | response := team.Invoke(context.Background(), "What is the stock price of Apple?") 60 | if response.Error != nil { 61 | panic(response.Error) 62 | } 63 | for chunk := range response.Stream { 64 | fmt.Print(chunk.Choices[0].Delta.Content()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/rag/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/redis/go-redis/v9" 10 | "github.com/showntop/llmack/rag" 11 | "github.com/showntop/llmack/vdb" 12 | vredis "github.com/showntop/llmack/vdb/redis" 13 | ) 14 | 15 | func main() { 16 | 17 | ctx := context.Background() 18 | 19 | config := &vredis.Config{} 20 | config.Addr = "127.0.0.1:6379" 21 | config.Password = "cdgxxx2025@tx" 22 | config.DB = 0 23 | config.Index = "vdb" 24 | // config = 1536 25 | config.FieldSchema = []*redis.FieldSchema{ 26 | {}, 27 | } 28 | 29 | indexer, err := rag.NewIndexer(vredis.Name, config) 30 | if err != nil { 31 | panic(err) 32 | } 33 | indexer.Index(ctx, mockDocuments(), &rag.SearchOptions{ 34 | LibraryID: 1, 35 | Kind: "text", 36 | IndexID: 1, 37 | TopK: 10, 38 | ScoreThreshold: 0.5, 39 | }) 40 | 41 | entities, err := indexer.Retrieve(ctx, "你好", rag.WithTopK(10), rag.WithScoreThreshold(0.5)) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | fmt.Println(entities) 47 | } 48 | 49 | func mockDocuments() []*vdb.Document { 50 | contents := ` 51 | 1. 埃菲尔铁塔:位于法国巴黎,是世界上最著名的地标之一,由古斯塔夫·埃菲尔设计,建于1889年。 52 | 2. 长城:位于中国,是世界七大奇迹之一,始建于秦朝至明朝,全长超过20000公里。 53 | 3. 大峡谷国家公园:位于美国亚利桑那州,以其深深的峡谷和壮丽的景色而闻名,它被科罗拉多河切割而成。 54 | 4. 罗马斗兽场:位于意大利罗马,建于公元70-80年间,是古罗马帝国最大的圆形竞技场。 55 | 5. 泰姬陵:位于印度阿格拉,由莫卧儿皇帝沙贾汗于1653年为纪念他的妻子而建成,是世界新七大奇迹之一。 56 | 6. 悉尼歌剧院:位于澳大利亚悉尼港,是20世纪最具标志性的建筑之一,以其独特的帆船设计而闻名。 57 | 7. 卢浮宫博物馆:位于法国巴黎,是世界上最大的博物馆之一,收藏丰富,包括达·芬奇的《蒙娜丽莎》和希腊的《米洛的维纳斯》。 58 | 8. 尼亚加拉大瀑布:位于美国和加拿大边境,由三个主要瀑布组成,其壮观的景色每年吸引数百万游客。 59 | 9. 圣索菲亚大教堂:位于土耳其伊斯坦布尔,最初建于公元537年,曾是东正教大教堂和清真寺,现为博物馆。 60 | 10. 马丘比丘:位于秘鲁安第斯山脉高原上的古印加遗址,世界新七大奇迹之一,海拔2400多米。 61 | ` 62 | 63 | var docs []*vdb.Document 64 | for idx, str := range strings.Split(contents, "\n") { 65 | if str == "" { 66 | continue 67 | } 68 | docs = append(docs, &vdb.Document{ 69 | ID: strconv.FormatInt(int64(idx+1), 10), 70 | Content: str, 71 | }) 72 | } 73 | return docs 74 | } 75 | -------------------------------------------------------------------------------- /prompt/format.go: -------------------------------------------------------------------------------- 1 | package prompt 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | var regex = regexp.MustCompile(`\{\{([a-zA-Z_][a-zA-Z0-9_]{0,29}|#histories#|#query#|#context#)\}\}`) 9 | var withVariableTmplRegex = regexp.MustCompile(`\{\{([a-zA-Z_][a-zA-Z0-9_]{0,29}|#[a-zA-Z0-9_]{1,50}\.[a-zA-Z0-9_\.]{1,100}#|#histories#|#query#|#context#)\}\}`) 10 | 11 | // TemplateFormatter ... 12 | type TemplateFormatter struct { 13 | template string 14 | withVariableTmpl bool 15 | regex *regexp.Regexp 16 | variableKeys []string 17 | } 18 | 19 | // NewTemplateFormatter ... 20 | func NewTemplateFormatter(template string, withVariableTmpl bool) *TemplateFormatter { 21 | regex := regex 22 | if withVariableTmpl { 23 | regex = withVariableTmplRegex 24 | } 25 | return &TemplateFormatter{ 26 | template: template, 27 | withVariableTmpl: withVariableTmpl, 28 | regex: regex, 29 | variableKeys: extract(template, regex), 30 | } 31 | } 32 | 33 | func extract(template string, regex *regexp.Regexp) []string { 34 | return regex.FindAllString(template, -1) 35 | } 36 | 37 | // Format ... 38 | func (p *TemplateFormatter) Format(inputs map[string]any, removeVars bool) string { 39 | replacer := func(match string) string { 40 | key := regex.FindStringSubmatch(match)[1] 41 | value, ok := inputs[key] 42 | if !ok { 43 | value = match 44 | } else { 45 | value = fmt.Sprintf("%+v", inputs[key]) 46 | } 47 | valuex, _ := value.(string) 48 | 49 | if removeVars { 50 | return removeTemplateVariables(valuex, p.withVariableTmpl) 51 | } 52 | return valuex 53 | } 54 | prompt := regex.ReplaceAllStringFunc(p.template, replacer) 55 | prompt = regexp.MustCompile(`<\|.*?\|>`).ReplaceAllString(prompt, "") 56 | return prompt 57 | } 58 | 59 | func removeTemplateVariables(text string, withVariableTmpl bool) string { 60 | regex := regex 61 | if withVariableTmpl { 62 | regex = withVariableTmplRegex 63 | } 64 | return regex.ReplaceAllString(text, "{$1}") 65 | } 66 | -------------------------------------------------------------------------------- /example/agent/finance/finance.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | "github.com/showntop/llmack/agent" 10 | "github.com/showntop/llmack/llm" 11 | "github.com/showntop/llmack/llm/deepseek" 12 | "github.com/showntop/llmack/log" 13 | ) 14 | 15 | var ( 16 | model = llm.NewInstance(deepseek.Name, llm.WithDefaultModel("deepseek-chat")) 17 | ) 18 | 19 | func init() { 20 | godotenv.Load() 21 | 22 | log.SetLogger(&log.WrapLogger{}) 23 | llm.WithSingleConfig(map[string]any{ 24 | "api_key": os.Getenv("deepseek_api_key"), 25 | }) 26 | } 27 | 28 | func main() { 29 | ctx := context.Background() 30 | 31 | agent := agent.NewAgent( 32 | "finance", 33 | agent.WithModel(model), 34 | agent.WithInstructions( 35 | ` 36 | You are a seasoned Wall Street analyst with deep expertise in market analysis! 📊 37 | 38 | Follow these steps for comprehensive financial analysis: 39 | 1. Market Overview 40 | - Latest stock price 41 | - 52-week high and low 42 | 2. Financial Deep Dive 43 | - Key metrics (P/E, Market Cap, EPS) 44 | 3. Professional Insights 45 | - Analyst recommendations breakdown 46 | - Recent rating changes 47 | 48 | 4. Market Context 49 | - Industry trends and positioning 50 | - Competitive analysis 51 | - Market sentiment indicators 52 | 53 | Your reporting style: 54 | - Begin with an executive summary 55 | - Use tables for data presentation 56 | - Include clear section headers 57 | - Add emoji indicators for trends (📈 📉) 58 | - Highlight key insights with bullet points 59 | - Compare metrics to industry averages 60 | - Include technical term explanations 61 | - End with a forward-looking analysis 62 | 63 | Risk Disclosure: 64 | - Always highlight potential risk factors 65 | - Note market uncertainties 66 | - Mention relevant regulatory concerns 67 | `), 68 | ) 69 | 70 | response := agent.Invoke(ctx, "小米科技的最新舆情和财务表现如何?") 71 | if response.Error != nil { 72 | panic(response.Error) 73 | } 74 | fmt.Println(response.Completion()) 75 | } 76 | -------------------------------------------------------------------------------- /speech/encoding.go: -------------------------------------------------------------------------------- 1 | package speech 2 | 3 | // PcmToWav TODO 4 | /* 5 | * 6 | chunk:二进制字符串 7 | numchannel:1=单声道,2=多声道 8 | saplerate:采样率 8000/16000 9 | */ 10 | func PcmToWav(chunk []byte, numchannel int, saplerate int) []byte { 11 | longSampleRate := saplerate 12 | byteRate := 16 * saplerate * numchannel / 8 13 | totalAudioLen := len(chunk) 14 | totalDataLen := totalAudioLen + 36 15 | var header = make([]byte, 44) 16 | // RIFF/WAVE header 17 | header[0] = 'R' 18 | header[1] = 'I' 19 | header[2] = 'F' 20 | header[3] = 'F' 21 | header[4] = byte(totalDataLen & 0xff) 22 | header[5] = byte((totalDataLen >> 8) & 0xff) 23 | header[6] = byte((totalDataLen >> 16) & 0xff) 24 | header[7] = byte((totalDataLen >> 24) & 0xff) 25 | // WAVE 26 | header[8] = 'W' 27 | header[9] = 'A' 28 | header[10] = 'V' 29 | header[11] = 'E' 30 | // 'fmt ' chunk 31 | header[12] = 'f' 32 | header[13] = 'm' 33 | header[14] = 't' 34 | header[15] = ' ' 35 | // 4 bytes: size of 'fmt ' chunk 36 | header[16] = 16 37 | header[17] = 0 38 | header[18] = 0 39 | header[19] = 0 40 | // format = 1 41 | header[20] = 1 42 | header[21] = 0 43 | header[22] = byte(numchannel) 44 | header[23] = 0 45 | header[24] = byte(longSampleRate & 0xff) 46 | header[25] = byte((longSampleRate >> 8) & 0xff) 47 | header[26] = byte((longSampleRate >> 16) & 0xff) 48 | header[27] = byte((longSampleRate >> 24) & 0xff) 49 | header[28] = byte(byteRate & 0xff) 50 | header[29] = byte((byteRate >> 8) & 0xff) 51 | header[30] = byte((byteRate >> 16) & 0xff) 52 | header[31] = byte((byteRate >> 24) & 0xff) 53 | // block align 54 | header[32] = byte(2 * 16 / 8) 55 | header[33] = 0 56 | // bits per sample 57 | header[34] = 16 58 | header[35] = 0 59 | // data 60 | header[36] = 'd' 61 | header[37] = 'a' 62 | header[38] = 't' 63 | header[39] = 'a' 64 | header[40] = byte(totalAudioLen & 0xff) 65 | header[41] = byte((totalAudioLen >> 8) & 0xff) 66 | header[42] = byte((totalAudioLen >> 16) & 0xff) 67 | header[43] = byte((totalAudioLen >> 24) & 0xff) 68 | 69 | return append(header, chunk...) 70 | } 71 | -------------------------------------------------------------------------------- /workflow/node/llm.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/showntop/llmack/llm" 8 | "github.com/showntop/llmack/prompt" 9 | "github.com/showntop/llmack/workflow" 10 | wf "github.com/showntop/llmack/workflow" 11 | ) 12 | 13 | // llmNode TODO 14 | type llmNode struct { 15 | Node *wf.Node 16 | 17 | executeable 18 | Identifier 19 | } 20 | 21 | // LLMNode 创建LLMNode 22 | func LLMNode(n *workflow.Node) *llmNode { 23 | return &llmNode{ 24 | Node: n, 25 | } 26 | } 27 | 28 | // Execute 执行LLM节点,单次执行 29 | func (n *llmNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) { 30 | // 解析metadata,获取模型配置 31 | stream, ok := n.Node.Metadata["stream"].(bool) 32 | if !ok { 33 | stream = true 34 | } 35 | provider, _ := n.Node.Metadata["provider"].(string) 36 | modelName, _ := n.Node.Metadata["model"].(string) 37 | systemPrompt, _ := n.Node.Metadata["system_prompt"].(string) 38 | userPrompt, _ := n.Node.Metadata["user_prompt"].(string) 39 | model := llm.NewInstance(provider, llm.WithDefaultModel(modelName)) 40 | 41 | // 处理 inputs @TODO default from query field 42 | messages := []llm.Message{} 43 | if systemPrompt != "" { 44 | newSystemPrompt, err := prompt.Render(systemPrompt, r.Inputs) 45 | if err != nil { 46 | return nil, err 47 | } 48 | messages = append(messages, llm.NewSystemMessage(newSystemPrompt)) 49 | } 50 | newUserPrompt, err := prompt.Render(userPrompt, r.Inputs) 51 | if err != nil { 52 | return nil, err 53 | } 54 | messages = append(messages, llm.NewUserTextMessage(newUserPrompt)) 55 | response, err := model.Invoke(ctx, messages, llm.WithStream(true)) 56 | if err != nil { 57 | return nil, err 58 | } 59 | if stream { 60 | return response, nil 61 | } 62 | result := response.Result() 63 | // result to map 64 | var mmm = map[string]any{ 65 | "message": map[string]any{ 66 | "content": strings.TrimLeft(strings.Trim(result.Message.Content(), "```"), "json"), 67 | }, 68 | "model": result.Model, 69 | "usage": result.Usage, 70 | } 71 | return mmm, nil 72 | } 73 | -------------------------------------------------------------------------------- /speech/asr/tencent/listener.go: -------------------------------------------------------------------------------- 1 | package tencent 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/showntop/tencentcloud-speech-sdk-go/asr" 8 | ) 9 | 10 | // DefaultListener implementation of SpeechRecognitionListener 11 | type DefaultListener struct { 12 | t *ASR 13 | ID int 14 | } 15 | 16 | // OnRecognitionStart implementation of SpeechRecognitionListener 17 | func (listener *DefaultListener) OnRecognitionStart(response *asr.SpeechRecognitionResponse) { 18 | fmt.Printf("%s|%s|OnRecognitionStart\n", time.Now().Format("2006-01-02 15:04:05"), response.VoiceID) 19 | } 20 | 21 | // OnSentenceBegin implementation of SpeechRecognitionListener 22 | func (listener *DefaultListener) OnSentenceBegin(response *asr.SpeechRecognitionResponse) { 23 | fmt.Printf("%s|%s|OnSentenceBegin: %v\n", time.Now().Format("2006-01-02 15:04:05"), response.VoiceID, response) 24 | } 25 | 26 | // OnRecognitionResultChange implementation of SpeechRecognitionListener 27 | func (listener *DefaultListener) OnRecognitionResultChange(response *asr.SpeechRecognitionResponse) { 28 | // fmt.Printf("OnRecognitionResultChange|%s|: %+v\n", response.MessageID, response) 29 | fmt.Printf("%s|%s|OnRecognitionResultChange: %v\n", time.Now().Format("2006-01-02 15:04:05"), response.VoiceID, response) 30 | } 31 | 32 | // OnSentenceEnd implementation of SpeechRecognitionListener 33 | func (listener *DefaultListener) OnSentenceEnd(response *asr.SpeechRecognitionResponse) { 34 | fmt.Printf("OnSentenceEnd|%s|: %+v\n", response.VoiceID, response) 35 | } 36 | 37 | // OnRecognitionComplete implementation of SpeechRecognitionListener 38 | func (listener *DefaultListener) OnRecognitionComplete(response *asr.SpeechRecognitionResponse) { 39 | fmt.Printf("%s|%s|OnRecognitionComplete\n", time.Now().Format("2006-01-02 15:04:05"), response.VoiceID) 40 | } 41 | 42 | // OnFail implementation of SpeechRecognitionListener 43 | func (listener *DefaultListener) OnFail(response *asr.SpeechRecognitionResponse, err error) { 44 | fmt.Printf("OnFail:%s|%s| %v\n", time.Now().Format("2006-01-02 15:04:05"), response.VoiceID, err) 45 | } 46 | -------------------------------------------------------------------------------- /vision/vision.go: -------------------------------------------------------------------------------- 1 | package vision 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | // Provider ... 9 | type Provider interface { 10 | GenerateImage(context.Context, string, ...InvokeOption) (string, error) 11 | } 12 | 13 | // Instance ... 14 | type Instance struct { 15 | name string 16 | provider Provider 17 | opts *Options 18 | } 19 | 20 | var providers = map[string]Provider{} 21 | 22 | // Register ... 23 | func Register(name string, provider Provider) { 24 | providers[name] = provider 25 | } 26 | 27 | // Options ... 28 | type Options struct { 29 | hooks []Hook 30 | model string 31 | } 32 | 33 | // Option ... 34 | type Option func(*Options) 35 | 36 | // WithHook ... 37 | // func WithHook(hooks ...Hook) Option { 38 | // return func(options *Options) { 39 | // options.hooks = append(options.hooks, hooks...) 40 | // } 41 | // } 42 | 43 | // NewInstance ... 44 | func NewInstance(provider string, opts ...Option) *Instance { 45 | var options Options = Options{} 46 | for _, o := range opts { 47 | o(&options) 48 | } 49 | 50 | return &Instance{ 51 | name: provider, 52 | opts: &options, 53 | provider: providers[provider], 54 | } 55 | } 56 | 57 | // Invoke ... 58 | func (mi *Instance) GenerateImage(ctx context.Context, 59 | prompt string, options ...InvokeOption) (string, error) { 60 | 61 | if mi.provider == nil { 62 | return "null", fmt.Errorf("llm provider of %v is not registered", mi.name) 63 | } 64 | if mi.opts != nil && mi.opts.hooks != nil { 65 | for _, hook := range mi.opts.hooks { 66 | ctx = hook.OnBeforeInvoke(ctx) 67 | } 68 | } 69 | 70 | // 删除 options 中 nil 71 | for i := 0; i < len(options); i++ { 72 | if options[i] == nil { 73 | options = append(options[:i], options[i+1:]...) 74 | i-- 75 | } 76 | } 77 | response, err := mi.provider.GenerateImage(ctx, prompt, options...) 78 | if err != nil { 79 | return response, err 80 | } 81 | 82 | if mi.opts != nil && mi.opts.hooks != nil { 83 | for _, hook := range mi.opts.hooks { 84 | hook.OnAfterInvoke(ctx, err) 85 | } 86 | } 87 | return response, err 88 | } 89 | -------------------------------------------------------------------------------- /example/rag/milvus/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/joho/godotenv" 11 | "github.com/showntop/llmack/embedding" 12 | "github.com/showntop/llmack/vdb" 13 | "github.com/showntop/llmack/vdb/milvus" 14 | ) 15 | 16 | func main() { 17 | godotenv.Load() 18 | 19 | stringEmbedder := embedding.NewStringEmbedder() 20 | vvv, err := milvus.New(&milvus.Config{ 21 | Address: os.Getenv("milvus_address"), 22 | CollectionName: "documents", 23 | Embedder: stringEmbedder, 24 | Distance: vdb.DistanceCosine, 25 | Dim: stringEmbedder.Dimension(), 26 | }) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | err = vvv.Create(context.Background()) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | err = vvv.Store(context.Background(), mockDocuments()...) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | docs, err := vvv.SearchQuery(context.Background(), "埃菲尔铁塔", vdb.WithTopk(2)) 42 | if err != nil { 43 | panic(err) 44 | } 45 | for _, doc := range docs { 46 | fmt.Println(doc.ID, doc.Title, doc.Content, doc.Embedding) 47 | } 48 | } 49 | 50 | func mockDocuments() []*vdb.Document { 51 | contents := ` 52 | 1. 埃菲尔铁塔:位于法国巴黎,是世界上最著名的地标之一,由古斯塔夫·埃菲尔设计,建于1889年。 53 | 2. 长城:位于中国,是世界七大奇迹之一,始建于秦朝至明朝,全长超过20000公里。 54 | 3. 大峡谷国家公园:位于美国亚利桑那州,以其深深的峡谷和壮丽的景色而闻名,它被科罗拉多河切割而成。 55 | 4. 罗马斗兽场:位于意大利罗马,建于公元70-80年间,是古罗马帝国最大的圆形竞技场。 56 | 5. 泰姬陵:位于印度阿格拉,由莫卧儿皇帝沙贾汗于1653年为纪念他的妻子而建成,是世界新七大奇迹之一。 57 | 6. 悉尼歌剧院:位于澳大利亚悉尼港,是20世纪最具标志性的建筑之一,以其独特的帆船设计而闻名。 58 | 7. 卢浮宫博物馆:位于法国巴黎,是世界上最大的博物馆之一,收藏丰富,包括达·芬奇的《蒙娜丽莎》和希腊的《米洛的维纳斯》。 59 | 8. 尼亚加拉大瀑布:位于美国和加拿大边境,由三个主要瀑布组成,其壮观的景色每年吸引数百万游客。 60 | 9. 圣索菲亚大教堂:位于土耳其伊斯坦布尔,最初建于公元537年,曾是东正教大教堂和清真寺,现为博物馆。 61 | 10. 马丘比丘:位于秘鲁安第斯山脉高原上的古印加遗址,世界新七大奇迹之一,海拔2400多米。 62 | ` 63 | 64 | var docs []*vdb.Document 65 | for idx, str := range strings.Split(contents, "\n") { 66 | if str == "" { 67 | continue 68 | } 69 | docs = append(docs, &vdb.Document{ 70 | ID: strconv.FormatInt(int64(idx+1), 10) + "X", 71 | Content: str, 72 | }) 73 | } 74 | return docs 75 | } 76 | -------------------------------------------------------------------------------- /speech/tts/aliyun/client.go: -------------------------------------------------------------------------------- 1 | package aliyun 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "sync" 7 | "time" 8 | 9 | "github.com/gorilla/websocket" 10 | ) 11 | 12 | // WsClient ... 13 | type WsClient struct { 14 | sync.Mutex 15 | ws *websocket.Conn 16 | } 17 | 18 | // NewWsClient ... 19 | func NewWsClient(token string) (*WsClient, error) { 20 | wsc := &WsClient{} 21 | 22 | // url := "wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1?token=%s" 23 | // url = fmt.Sprintf(url, token) 24 | 25 | // dialer := websocket.Dialer{} 26 | // header := http.Header(make(map[string][]string)) 27 | // conn, _, err := dialer.Dial(url, header) 28 | // if err != nil { 29 | // return nil, err 30 | // } 31 | // wsc.ws = conn 32 | // fmt.Println("new") 33 | 34 | // go wsc.keepAlive() 35 | 36 | return wsc, nil 37 | } 38 | 39 | // reconnect ... 40 | func (c *WsClient) reconnect(token string) error { 41 | fmt.Println("reconnecting 1") 42 | 43 | c.Lock() 44 | defer c.Unlock() 45 | 46 | url := "wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1?token=%s" 47 | url = fmt.Sprintf(url, token) 48 | fmt.Println("reconnecting") 49 | dialer := websocket.Dialer{} 50 | header := http.Header(make(map[string][]string)) 51 | conn, _, err := dialer.Dial(url, header) 52 | if err != nil { 53 | return err 54 | } 55 | fmt.Println("reconnecting done") 56 | c.ws = conn 57 | 58 | return nil 59 | } 60 | 61 | // WriteJSON ... 62 | func (c *WsClient) WriteJSON(x any) error { 63 | c.Lock() 64 | defer c.Unlock() 65 | 66 | return c.ws.WriteJSON(x) 67 | } 68 | 69 | func (c *WsClient) keepAlive() { 70 | for { 71 | fmt.Println("keepAlive") 72 | c.Lock() 73 | if err := c.ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(10*time.Second)); err != nil { 74 | panic(err) 75 | } 76 | c.Unlock() 77 | 78 | time.Sleep(9 * time.Second) 79 | } 80 | } 81 | 82 | // Close ... 83 | func (c *WsClient) close() error { 84 | c.Lock() 85 | defer c.Unlock() 86 | 87 | return c.ws.Close() 88 | } 89 | 90 | // // Close ... 91 | // func (c *WsClient) Write() error { 92 | // return c.ws.Close() 93 | // } 94 | -------------------------------------------------------------------------------- /tool/crawl/colly.go: -------------------------------------------------------------------------------- 1 | package crawl 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/gocolly/colly/v2" 8 | "github.com/showntop/llmack/log" 9 | ) 10 | 11 | func init() { 12 | Register(Jina, NewCollyCrawler()) 13 | } 14 | 15 | // CollyCrawler 使用jina搜索 16 | type CollyCrawler struct { 17 | } 18 | 19 | // NewCollyCrawler 创建jina爬虫 20 | func NewCollyCrawler() Crawler { 21 | return &CollyCrawler{} 22 | } 23 | 24 | // CrawlMulti 爬取网页内容 25 | func (s *CollyCrawler) CrawlMulti(ctx context.Context, urls []string) (map[string]*Result, error) { 26 | log.InfoContextf(ctx, "tool spider crawl urls: %+v", urls) 27 | // 并发爬取 28 | wg := sync.WaitGroup{} 29 | resultChan := make(chan *Result, len(urls)) 30 | for _, url := range urls { 31 | wg.Add(1) 32 | go func(url string) { 33 | defer wg.Done() 34 | content, err := s.Crawl(ctx, url) 35 | if err != nil { 36 | log.ErrorContextf(ctx, "tool spider crawl url %s failed: %v", url, err) 37 | } 38 | resultChan <- content 39 | }(url) 40 | } 41 | 42 | go func() { 43 | wg.Wait() 44 | close(resultChan) 45 | }() 46 | 47 | outputs := make(map[string]*Result, len(urls)) 48 | for item := range resultChan { 49 | outputs[item.Link] = item 50 | } 51 | log.InfoContextf(ctx, "tool spider crawl result: %+v", len(outputs)) 52 | return outputs, nil 53 | } 54 | 55 | // Crawl ... 56 | func (s *CollyCrawler) Crawl(ctx context.Context, url string) (*Result, error) { 57 | 58 | var title string 59 | var content string 60 | collector := colly.NewCollector( 61 | // colly.AllowedDomains("www.baidu.com"), 62 | colly.MaxDepth(1), 63 | colly.Async(true), 64 | ) 65 | collector.OnHTML("title", func(e *colly.HTMLElement) { 66 | // log.InfoContextf(ctx, "title: %s", e.Text) 67 | title = e.Text 68 | }) 69 | 70 | collector.OnHTML("body", func(e *colly.HTMLElement) { 71 | content = string(e.Text) 72 | log.InfoContextf(ctx, "tool spider crawl url %s response: %d", url, len(content)) 73 | }) 74 | collector.Visit(url) 75 | collector.Wait() 76 | 77 | return &Result{ 78 | Title: title, 79 | Content: content, 80 | Link: url, 81 | }, nil 82 | } 83 | -------------------------------------------------------------------------------- /pkg/structx/map.go: -------------------------------------------------------------------------------- 1 | package structx 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | // ToStringMap converts a map[string]any to map[string]string. 9 | // It iterates through the input map and includes only the key-value pairs 10 | // where the value is of type string. 11 | func ToStringMap(inputMap map[string]any) map[string]string { 12 | resultMap := make(map[string]string) 13 | if inputMap == nil { 14 | return resultMap 15 | } 16 | for key, value := range inputMap { 17 | if strValue, ok := value.(string); ok { 18 | resultMap[key] = strValue 19 | } 20 | } 21 | return resultMap 22 | } 23 | 24 | // GetDefaultValue returns the default value if the key is not found in the data 25 | // Example: GetDefaultValue(data, "headless", false) 26 | func GetDefaultValue[T any](data map[string]any, key string, defaultValue T) T { 27 | if value, ok := data[key]; ok { 28 | if value, ok := value.(T); ok { 29 | return value 30 | } 31 | } 32 | return defaultValue 33 | } 34 | 35 | func ToSliceOfInt(input any) ([]int, error) { 36 | if input == nil { 37 | return nil, fmt.Errorf("input cannot be nil") 38 | } 39 | slice, ok := input.([]any) 40 | if !ok { 41 | return nil, fmt.Errorf("input is not a slice of interface") 42 | } 43 | result := make([]int, 0, len(slice)) 44 | 45 | for i, elem := range slice { 46 | if elem == nil { 47 | return nil, fmt.Errorf("element at index %d is nil", i) 48 | } 49 | // if typeof elem is string, convert to int using strconv.Atoi, if it's int, just use it 50 | if strValue, ok := elem.(string); ok { 51 | value, err := strconv.Atoi(strValue) 52 | if err != nil { 53 | return nil, fmt.Errorf("failed to convert string to int: %s", err) 54 | } 55 | result = append(result, value) 56 | continue 57 | } 58 | value, ok := elem.(int) 59 | if !ok { 60 | return nil, fmt.Errorf("element at index %d is not of type %T, but %T", i, value, elem) 61 | } 62 | result = append(result, value) 63 | } 64 | return result, nil 65 | } 66 | 67 | func ToOptional[T any](value any) *T { 68 | if value, ok := value.(T); ok { 69 | return &value 70 | } 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /workflow/node/end.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "sync" 7 | 8 | "github.com/showntop/flatmap" 9 | 10 | "github.com/showntop/llmack/llm" 11 | "github.com/showntop/llmack/workflow" 12 | wf "github.com/showntop/llmack/workflow" 13 | ) 14 | 15 | type endNode struct { 16 | Node *wf.Node 17 | 18 | executeable 19 | Identifier 20 | } 21 | 22 | func (nd endNode) Schema() string { 23 | return `{ 24 | "kind": "end", 25 | "outputs": [ 26 | { 27 | "name": "result", 28 | "type": "json" 29 | } 30 | ] 31 | }` 32 | } 33 | 34 | // EndNode initializes new end node with end definition 35 | func EndNode(node *workflow.Node) (*endNode, error) { 36 | return &endNode{Node: node}, nil 37 | } 38 | 39 | // Exec executes end node 40 | func (nd endNode) Execute(ctx context.Context, r *ExecRequest) (ExecResponse, error) { 41 | result := make(map[string]any) 42 | 43 | // 处理输出 44 | // log.InfoContextf(ctx, "end node %+v inputs: %+v", nd.Node.Metadata, r.Inputs) 45 | // extract args 46 | mp, err := flatmap.Flatten(r.Inputs, flatmap.DefaultTokenizer) 47 | if err != nil { 48 | return result, err 49 | } 50 | // log.InfoContextf(ctx, "end node %+v inputs: %+v", nd.Node.Metadata, mp) 51 | 52 | wg := sync.WaitGroup{} 53 | wg.Add(len(nd.Node.Outputs)) 54 | for name, p := range nd.Node.Outputs { 55 | pointer := strings.TrimPrefix(p.Value, "{{") 56 | pointer = strings.TrimSuffix(pointer, "}}") 57 | value := mp.Get(pointer) 58 | if response, ok := value.(*llm.Response); ok { 59 | go func() { 60 | stream := response.Stream() 61 | for chunk := stream.Take(); chunk != nil; chunk = stream.Take() { 62 | r.Events <- &workflow.Event{ 63 | Name: name, 64 | Data: chunk, 65 | Type: "end", 66 | } 67 | } 68 | wg.Done() 69 | }() 70 | } else { 71 | r.Events <- &workflow.Event{ 72 | Name: name, 73 | Data: value, 74 | Type: "end", 75 | } 76 | wg.Done() 77 | } 78 | result[name] = value 79 | } 80 | r.Events <- &workflow.Event{ 81 | Name: "end", 82 | Data: result, 83 | } 84 | wg.Wait() 85 | close(r.Events) 86 | return result, nil 87 | } 88 | -------------------------------------------------------------------------------- /prompt/simple_format.go: -------------------------------------------------------------------------------- 1 | package prompt 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "strings" 7 | 8 | "github.com/showntop/llmack/llm" 9 | "github.com/showntop/llmack/log" 10 | ) 11 | 12 | // SimplePromptFormatter ... 13 | type SimplePromptFormatter struct { 14 | } 15 | 16 | // Format ... 17 | func (p *SimplePromptFormatter) Format(preset string, 18 | inputs map[string]any, query string, contexts string) ([]llm.Message, []string) { 19 | if preset != "" { 20 | formatter := NewTemplateFormatter(preset, false) 21 | preset = formatter.Format(inputs, true) 22 | } 23 | log.DefaultLogger().InfoContextf(context.Background(), "contexts: %v", contexts) 24 | frame := p.getPromptFrame() 25 | 26 | sysPrompt := "" 27 | variables := []string{} 28 | for _, v := range frame["system_prompt_orders"].([]any) { 29 | vname := v.(string) 30 | log.DefaultLogger().InfoContextf(context.Background(), "vname: %v", vname) 31 | if "context" == vname && contexts != "" { 32 | if c, ok := frame[vname].(string); ok { 33 | sysPrompt += c 34 | } 35 | } 36 | if "query" == vname && query != "" { 37 | if c, ok := frame[vname].(string); ok { 38 | sysPrompt += c 39 | } 40 | } 41 | if "preset" == vname { 42 | sysPrompt += preset + "\n" 43 | } 44 | 45 | variables = append(variables, "#"+v.(string)+"#") 46 | } 47 | 48 | sysPrompt = strings.ReplaceAll(sysPrompt, "{{#context#}}", contexts) 49 | 50 | sysMessage := llm.NewSystemMessage(sysPrompt) 51 | 52 | stops, ok := frame["stops"].(string) 53 | if ok { 54 | return []llm.Message{sysMessage}, []string{stops} 55 | } 56 | return []llm.Message{sysMessage}, nil 57 | } 58 | 59 | const simplePromptFrame = `{ 60 | "context": "用户在与一个客观的助手对话。助手会尽量尊重找到的材料,给出全面专业的解释,但不会过度演绎。以下为材料内容:\n\n'''\n{{#context#}}\n'''\n,以下为用户的要求:\n", 61 | "system_prompt_orders": [ 62 | "context", 63 | "preset" 64 | ], 65 | "query": "{{#query#}}", 66 | "stops": null 67 | }` 68 | 69 | func (p *SimplePromptFormatter) getPromptFrame() map[string]any { 70 | // os.ReadFile("./simple_prompt.json") 71 | prompt := make(map[string]any) 72 | json.Unmarshal([]byte(simplePromptFrame), &prompt) 73 | return prompt 74 | } 75 | -------------------------------------------------------------------------------- /example/bookkeeper/web/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: 13 | 14 | ```js 15 | export default tseslint.config({ 16 | extends: [ 17 | // Remove ...tseslint.configs.recommended and replace with this 18 | ...tseslint.configs.recommendedTypeChecked, 19 | // Alternatively, use this for stricter rules 20 | ...tseslint.configs.strictTypeChecked, 21 | // Optionally, add this for stylistic rules 22 | ...tseslint.configs.stylisticTypeChecked, 23 | ], 24 | languageOptions: { 25 | // other options... 26 | parserOptions: { 27 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 28 | tsconfigRootDir: import.meta.dirname, 29 | }, 30 | }, 31 | }) 32 | ``` 33 | 34 | You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: 35 | 36 | ```js 37 | // eslint.config.js 38 | import reactX from 'eslint-plugin-react-x' 39 | import reactDom from 'eslint-plugin-react-dom' 40 | 41 | export default tseslint.config({ 42 | plugins: { 43 | // Add the react-x and react-dom plugins 44 | 'react-x': reactX, 45 | 'react-dom': reactDom, 46 | }, 47 | rules: { 48 | // other rules... 49 | // Enable its recommended typescript rules 50 | ...reactX.configs['recommended-typescript'].rules, 51 | ...reactDom.configs.recommended.rules, 52 | }, 53 | }) 54 | ``` 55 | -------------------------------------------------------------------------------- /llm/cache_redis.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-redis/redis/v8" 7 | "github.com/google/uuid" 8 | "github.com/showntop/llmack/embedding" 9 | "github.com/showntop/llmack/vdb" 10 | "github.com/showntop/llmack/vdb/memo" 11 | ) 12 | 13 | // RedisCache ... 14 | type RedisCache struct { 15 | BaseCache 16 | cli *redis.Client 17 | } 18 | 19 | // NewRedisCache ... 20 | func NewRedisCache(cli *redis.Client) Cache { 21 | return &RedisCache{ 22 | BaseCache: BaseCache{ 23 | Config: DefaultConfig(), 24 | Embedder: embedding.NewStringEmbedder(), 25 | VectorStore: memo.New(), 26 | }, 27 | cli: cli, 28 | } 29 | } 30 | 31 | // Fetch ... 32 | func (m *RedisCache) Fetch(ctx context.Context, messages []Message) (*CachedDocument, bool, error) { 33 | // 处理query 34 | query, err := m.Config.QueryProcessor(messages) 35 | if err != nil { 36 | return nil, false, err 37 | } 38 | // embedding vector 39 | vector, err := m.Embedder.Embed(ctx, query) 40 | if err != nil { 41 | return nil, false, err 42 | } 43 | // search vector ids 44 | relations, err := m.VectorStore.Search(ctx, vector, vdb.WithTopk(5)) 45 | if err != nil { 46 | return nil, false, err 47 | } 48 | if relations == nil || len(relations) == 0 { 49 | return &CachedDocument{Query: query, Vector: vector}, false, nil 50 | } 51 | ids := make([]string, 0, len(relations)) 52 | for i := 0; i < len(relations); i++ { 53 | ids[i] = relations[i].ID 54 | } 55 | // batch get 56 | res, err := m.cli.MGet(ctx, ids...).Result() 57 | if err != nil { 58 | return nil, false, err 59 | } 60 | _ = res 61 | // object data 62 | documents := make([]*CachedDocument, 0, len(relations)) 63 | 64 | for i := 0; i < len(relations); i++ { 65 | 66 | } 67 | if len(documents) == 0 { 68 | return nil, false, nil 69 | } 70 | 71 | // 排序 72 | document := documents[0] 73 | return document, true, nil 74 | } 75 | 76 | // Store ... 77 | func (m *RedisCache) Store(ctx context.Context, document *CachedDocument, value string) error { 78 | document.ID = uuid.NewString() 79 | // if err := m.VectorStore.Store(ctx, document.ID, document.Vector); err != nil { 80 | // return err 81 | // } 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /program/response.go: -------------------------------------------------------------------------------- 1 | package program 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "reflect" 7 | 8 | "github.com/showntop/llmack/llm" 9 | "github.com/showntop/llmack/log" 10 | ) 11 | 12 | func (p *predictor) Response() *Response { 13 | return p.reponse 14 | } 15 | 16 | func (p *predictor) Stream() chan *llm.Chunk { 17 | return p.reponse.stream 18 | } 19 | 20 | func (p *predictor) Completion() string { 21 | return p.reponse.message.Content() 22 | } 23 | 24 | func (p *predictor) Message() *llm.AssistantMessage { 25 | return p.reponse.message 26 | } 27 | 28 | func (p *predictor) Error() error { 29 | return p.reponse.err 30 | } 31 | 32 | func (p *predictor) ToolCalls() []*llm.ToolCall { 33 | return p.reponse.toolCalls 34 | } 35 | 36 | type Response struct { 37 | p *predictor 38 | err error 39 | completion string 40 | toolCalls []*llm.ToolCall 41 | stream chan *llm.Chunk 42 | 43 | message *llm.AssistantMessage 44 | 45 | HasToolCalls bool 46 | } 47 | 48 | func NewResponse() *Response { 49 | return &Response{ 50 | stream: make(chan *llm.Chunk, 1000), 51 | message: &llm.AssistantMessage{}, 52 | } 53 | } 54 | 55 | func (r *Response) Error() error { 56 | return r.err 57 | } 58 | 59 | func (r *Response) Get(value any) error { 60 | return r.p.adapter.Parse(r.completion, value) 61 | } 62 | 63 | func (r *Response) Completion() string { 64 | return r.message.Content() 65 | } 66 | 67 | func (r *Response) ToolCalls() []*llm.ToolCall { 68 | return r.toolCalls 69 | } 70 | 71 | func (r *Response) Stream() chan *llm.Chunk { 72 | return r.stream 73 | } 74 | 75 | // Result ... 76 | func (p *predictor) Result(ctx context.Context, value any) error { 77 | if reflect.TypeOf(value).Kind() != reflect.Ptr { 78 | return fmt.Errorf("predictor result target value must be a pointer") 79 | } 80 | 81 | messages, err := p.adapter.Format(p, p.inputs, value) 82 | if err != nil { 83 | return err 84 | } 85 | response, err := p.model.Invoke(ctx, messages, 86 | llm.WithStream(true), 87 | ) 88 | if err != nil { 89 | return err 90 | } 91 | completion := response.Result().Message.Content() 92 | log.InfoContextf(ctx, "response: %s", completion) 93 | 94 | return p.adapter.Parse(completion, value) 95 | } 96 | -------------------------------------------------------------------------------- /rag/deepdoc/extractor/document.go: -------------------------------------------------------------------------------- 1 | package extractor 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/showntop/llmack/log" 7 | "github.com/showntop/llmack/rag/deepdoc" 8 | ) 9 | 10 | // DocumentExtractor ... 11 | type DocumentExtractor struct { 12 | } 13 | 14 | // NewDocumentExtractor ... 15 | func NewDocumentExtractor() *DocumentExtractor { 16 | return &DocumentExtractor{} 17 | } 18 | 19 | // Extract ... 20 | func (e *DocumentExtractor) Extract(m *Meta, binary []byte) ([]string, error) { 21 | filename := m.Filename 22 | var sections []string 23 | var err error 24 | if v := reDocx.FindStringIndex(filename); v != nil { // docx 25 | sections, err = deepdoc.Docx().Extract(filename, binary) 26 | } else if v := reXlsx.FindStringIndex(filename); v != nil { 27 | sections, err = deepdoc.Excel().Extract(filename, binary) 28 | } else if v := rePptx.FindStringIndex(filename); v != nil { 29 | sections, err = deepdoc.Pptx().Extract(filename, binary) 30 | } else if v := rePdf.FindStringIndex(filename); v != nil { 31 | sections, err = deepdoc.Pdf().Extract(filename, binary) 32 | } else if v := reTxtx.FindStringIndex(filename); v != nil { 33 | sections, err = deepdoc.Txtx().Extract(filename, binary) 34 | } else if v := reDoc.FindStringIndex(filename); v != nil { 35 | sections, err = deepdoc.Doc().Extract(filename, binary) 36 | } else if v := reCsv.FindStringIndex(filename); v != nil { 37 | sections, err = deepdoc.Csv().Extract(filename, binary) 38 | } else if v := reJson.FindStringIndex(filename); v != nil { 39 | sections, err = deepdoc.Json().Extract(filename, binary) 40 | } else if v := reMdx.FindStringIndex(filename); v != nil { 41 | sections, err = deepdoc.Mdx().Extract(filename, binary) 42 | } else { 43 | log.WarnContextf(nil, "extract unknow document filename: %s by txt", filename) 44 | sections, err = deepdoc.Txtx().Extract(filename, binary) 45 | // return nil, fmt.Errorf("unsupported file format: %s", filename) 46 | } 47 | if err != nil { 48 | // 兜底 解析 按照文本 49 | log.WarnContextf(nil, "extract document error: %v, filename: %s", err, filename) 50 | sections, err = deepdoc.Txtx().Extract(filename, binary) 51 | return nil, err 52 | } 53 | 54 | return []string{strings.Join(sections, "")}, nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/adb/manager.go: -------------------------------------------------------------------------------- 1 | package adb 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | // Manager 设备管理器 9 | type Manager struct { 10 | wrapper *Wrapper 11 | devices map[string]*Device 12 | } 13 | 14 | // NewManager 创建新的设备管理器 15 | func NewManager(adbPath string) *Manager { 16 | return &Manager{ 17 | wrapper: NewWrapper(adbPath), 18 | devices: make(map[string]*Device), 19 | } 20 | } 21 | 22 | // ListDevices 列出连接的设备 23 | func (m *Manager) ListDevices(ctx context.Context) ([]*Device, error) { 24 | devicesInfo, err := m.wrapper.GetDevices(ctx) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | // 更新设备缓存 30 | currentSerials := make(map[string]bool) 31 | for _, deviceInfo := range devicesInfo { 32 | serial := deviceInfo.Serial 33 | currentSerials[serial] = true 34 | 35 | if _, exists := m.devices[serial]; !exists { 36 | m.devices[serial] = NewDevice(serial, m.wrapper) 37 | } 38 | } 39 | 40 | // 移除已断开连接的设备 41 | for serial := range m.devices { 42 | if !currentSerials[serial] { 43 | delete(m.devices, serial) 44 | } 45 | } 46 | 47 | // 返回设备列表 48 | var devices []*Device 49 | for _, device := range m.devices { 50 | devices = append(devices, device) 51 | } 52 | 53 | return devices, nil 54 | } 55 | 56 | // GetDevice 获取特定设备 57 | func (m *Manager) GetDevice(ctx context.Context, serial string) (*Device, error) { 58 | if device, exists := m.devices[serial]; exists { 59 | return device, nil 60 | } 61 | 62 | // 尝试查找设备 63 | devices, err := m.ListDevices(ctx) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | for _, device := range devices { 69 | if device.Serial == serial { 70 | return device, nil 71 | } 72 | } 73 | 74 | return nil, fmt.Errorf("设备 %s 未找到", serial) 75 | } 76 | 77 | // Connect 连接到网络设备 78 | func (m *Manager) Connect(ctx context.Context, host string, port int) (*Device, error) { 79 | serial, err := m.wrapper.Connect(ctx, host, port) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | return m.GetDevice(ctx, serial) 85 | } 86 | 87 | // Disconnect 断开设备连接 88 | func (m *Manager) Disconnect(ctx context.Context, serial string) error { 89 | err := m.wrapper.Disconnect(ctx, serial) 90 | if err == nil { 91 | delete(m.devices, serial) 92 | } 93 | return err 94 | } 95 | -------------------------------------------------------------------------------- /example/bookkeeper/services/image_service.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "mime/multipart" 8 | "os" 9 | "path/filepath" 10 | "time" 11 | 12 | "github.com/bookkeeper-ai/bookkeeper/config" 13 | "github.com/showntop/llmack/llm" 14 | "github.com/showntop/llmack/llm/zhipu" 15 | ) 16 | 17 | type ImageService struct { 18 | uploadDir string 19 | llmClient *llm.Instance 20 | } 21 | 22 | func NewImageService(cfg *config.Config) (*ImageService, error) { 23 | uploadDir := "uploads" 24 | if err := os.MkdirAll(uploadDir, 0755); err != nil { 25 | return nil, fmt.Errorf("创建上传目录失败: %v", err) 26 | } 27 | 28 | llmClient := llm.NewInstance(zhipu.Name) 29 | // llm.WithSingleConfig(map[string]any{ 30 | // "api_key": os.Getenv("ZHIPU_API_KEY"), 31 | // }) 32 | 33 | return &ImageService{ 34 | uploadDir: uploadDir, 35 | llmClient: llmClient, 36 | }, nil 37 | } 38 | 39 | // UploadAndAnalyze 上传并分析图片 40 | func (s *ImageService) UploadAndAnalyze(file *multipart.FileHeader) (string, map[string]interface{}, error) { 41 | // 保存文件 42 | filename := fmt.Sprintf("%d_%s", time.Now().Unix(), filepath.Base(file.Filename)) 43 | filepath := filepath.Join(s.uploadDir, filename) 44 | 45 | src, err := file.Open() 46 | if err != nil { 47 | return "", nil, fmt.Errorf("打开文件失败: %v", err) 48 | } 49 | defer src.Close() 50 | 51 | dst, err := os.Create(filepath) 52 | if err != nil { 53 | return "", nil, fmt.Errorf("创建文件失败: %v", err) 54 | } 55 | defer dst.Close() 56 | 57 | if _, err = io.Copy(dst, src); err != nil { 58 | return "", nil, fmt.Errorf("保存文件失败: %v", err) 59 | } 60 | 61 | // 使用 LLM 分析图片 62 | ctx := context.Background() 63 | resp, err := s.llmClient.Invoke(ctx, []llm.Message{ 64 | llm.NewUserTextMessage(fmt.Sprintf("请分析这张消费小票图片,提取以下信息:1. 消费金额 2. 消费类别 3. 消费日期 4. 商家名称。图片路径:%s", filepath)), 65 | }, nil, llm.WithModel("GLM-4-Flash")) 66 | 67 | if err != nil { 68 | return "", nil, fmt.Errorf("分析图片失败: %v", err) 69 | } 70 | 71 | // 解析 LLM 返回的结果 72 | // TODO: 根据实际返回格式解析结果 73 | result := map[string]interface{}{ 74 | "amount": 0.0, 75 | "category": "", 76 | "date": time.Now(), 77 | "merchant": "", 78 | "raw_result": resp.Result(), 79 | } 80 | 81 | return filepath, result, nil 82 | } 83 | -------------------------------------------------------------------------------- /llm/hook.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "database/sql/driver" 7 | "io" 8 | 9 | "go.opentelemetry.io/otel" 10 | "go.opentelemetry.io/otel/attribute" 11 | "go.opentelemetry.io/otel/codes" 12 | "go.opentelemetry.io/otel/trace" 13 | ) 14 | 15 | // Hook ... 16 | type Hook interface { 17 | OnBeforeInvoke(context.Context) context.Context 18 | OnAfterInvoke(ctx context.Context, err error) 19 | OnFirstChunk(context.Context, error) context.Context 20 | OnLastChunk(context.Context, error) 21 | } 22 | 23 | // OtelHook ... 24 | type OtelHook struct { 25 | provider trace.TracerProvider 26 | tracer trace.Tracer 27 | } 28 | 29 | // HookOption ... 30 | type HookOption func(*OtelHook) 31 | 32 | // NewOtelHook ... 33 | func NewOtelHook(opts ...HookOption) Hook { 34 | h := &OtelHook{} 35 | h.provider = otel.GetTracerProvider() 36 | h.tracer = h.provider.Tracer("github.com/showntop/llmack/opentelemetry") 37 | 38 | return h 39 | } 40 | 41 | // OnFirstChunk ... 42 | func (h *OtelHook) OnFirstChunk(ctx context.Context, _ error) context.Context { 43 | ctx, _ = h.tracer.Start(ctx, "llm/response", trace.WithSpanKind(trace.SpanKindInternal)) 44 | 45 | return ctx 46 | } 47 | 48 | // OnLastChunk ... 49 | func (h *OtelHook) OnLastChunk(ctx context.Context, err error) { 50 | span := trace.SpanFromContext(ctx) 51 | if !span.IsRecording() { 52 | return 53 | } 54 | defer span.End() 55 | if err != nil { 56 | span.RecordError(err) 57 | span.SetStatus(codes.Error, err.Error()) 58 | } 59 | } 60 | 61 | // OnBeforeInvoke ... 62 | func (h *OtelHook) OnBeforeInvoke(ctx context.Context) context.Context { 63 | ctx, _ = h.tracer.Start(ctx, "llm/invoke", trace.WithSpanKind(trace.SpanKindInternal)) 64 | return ctx 65 | } 66 | 67 | // OnAfterInvoke ... 68 | func (h *OtelHook) OnAfterInvoke(ctx context.Context, err error) { 69 | span := trace.SpanFromContext(ctx) 70 | if !span.IsRecording() { 71 | return 72 | } 73 | defer span.End() 74 | 75 | attrs := make([]attribute.KeyValue, 0, 4) 76 | 77 | span.SetAttributes(attrs...) 78 | switch err { 79 | case nil, 80 | driver.ErrSkip, 81 | io.EOF, // end of rows iterator 82 | sql.ErrNoRows: 83 | // ignore 84 | default: 85 | span.RecordError(err) 86 | span.SetStatus(codes.Error, err.Error()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /speech/asr/tencent/tencent.go: -------------------------------------------------------------------------------- 1 | package tencent 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/showntop/llmack/log" 8 | "github.com/showntop/llmack/speech" 9 | "github.com/showntop/tencentcloud-speech-sdk-go/asr" 10 | "github.com/showntop/tencentcloud-speech-sdk-go/common" 11 | ) 12 | 13 | // ASR ... 14 | type ASR struct { 15 | options Options 16 | recognizer *asr.SpeechRecognizer 17 | } 18 | 19 | // NewASR 新建一个ASR 20 | func NewASR(opts ...Option) speech.ASR { 21 | t := &ASR{} 22 | 23 | for i := 0; i < len(opts); i++ { 24 | opts[i](&t.options) 25 | } 26 | 27 | if t.options.listener == nil { 28 | t.options.listener = &DefaultListener{ 29 | t: t, 30 | ID: 1, 31 | } 32 | } 33 | 34 | EngineModelType := "16k_zh" 35 | credential := common.NewCredential(t.options.SecretID, t.options.SecretKey) 36 | recognizer := asr.NewSpeechRecognizer(t.options.AppID, credential, EngineModelType, t.options.listener) 37 | 38 | recognizer.VoiceFormat = asr.AudioFormatPCM 39 | // recognizer.VoiceFormat = asr.AudioFormatWav 40 | // recognizer.NeedVad = 1 41 | recognizer.NoiseThreshold = 0.8 42 | 43 | // recognizer.VoiceFormat = asr.AudioFormatWav 44 | if err := recognizer.Start(); err != nil { 45 | panic(err) 46 | } 47 | log.InfoContextf(context.Background(), "asr engine start success") 48 | t.recognizer = recognizer 49 | // fmt.Println(t.recognizer) 50 | return t 51 | } 52 | 53 | // Input 异步实现语音识别,转录为文字 54 | func (t *ASR) Input(content []byte) error { 55 | if len(content) == 0 { 56 | return nil 57 | } 58 | 59 | startTime := time.Now() 60 | // 置换 chunk 状态 61 | chunkSizePerDuration := float64(48000) * 2.00 / float64(time.Second) 62 | durations := time.Duration(float64(len(content)) / chunkSizePerDuration) 63 | if err := t.recognizer.Write(content); err != nil { 64 | panic(err) 65 | } 66 | endTime := time.Now() 67 | 68 | x := durations - endTime.Sub(startTime) 69 | // fmt.Printf("size:%d, chunk: %f durations: %s, sleep x=%s \n", len(content), chunkSizePerDuration, durations, x) 70 | if x > 0 { 71 | time.Sleep(x) 72 | } 73 | return nil 74 | } 75 | 76 | // Recognize 实现语音识别,转录为文字 77 | func (t *ASR) Recognize(content []byte) (string, error) { 78 | panic("implement me") 79 | } 80 | 81 | // Close 关闭 82 | func (t *ASR) Close() error { 83 | return t.recognizer.Stop() 84 | } 85 | -------------------------------------------------------------------------------- /storage/json_store.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | type JSONStorage struct { 12 | path string 13 | } 14 | 15 | func NewJSONStorage(path string) Storage { 16 | if _, err := os.Stat(path); os.IsNotExist(err) { 17 | if err := os.MkdirAll(path, 0755); err != nil { 18 | panic(err) 19 | } 20 | } 21 | 22 | return &JSONStorage{ 23 | path: path, 24 | } 25 | } 26 | 27 | func (s *JSONStorage) AddNewJourney(ctx context.Context, journey *Journey) error { 28 | panic("not implemented") 29 | } 30 | 31 | func (s *JSONStorage) SaveSession(ctx context.Context, session *Session) error { 32 | // create file 33 | filePath := filepath.Join(s.path, session.UID+".json") 34 | file, err := os.Create(filePath) 35 | if err != nil { 36 | return err 37 | } 38 | defer file.Close() 39 | 40 | return json.NewEncoder(file).Encode(session) 41 | } 42 | 43 | func (s *JSONStorage) FetchSession(ctx context.Context, id string) (*Session, error) { 44 | filePath := filepath.Join(s.path, id+".json") 45 | // 如果不存在就创建 46 | if _, err := os.Stat(filePath); os.IsNotExist(err) { 47 | _, err := os.Create(filePath) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return &Session{ 52 | UID: id, 53 | }, nil 54 | } 55 | file, err := os.Open(filePath) 56 | if err != nil { 57 | return nil, err 58 | } 59 | defer file.Close() 60 | 61 | content, err := io.ReadAll(file) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | if len(content) == 0 { 67 | return &Session{ 68 | UID: id, 69 | }, nil 70 | } 71 | 72 | var session Session 73 | if err := json.Unmarshal(content, &session); err != nil { 74 | return nil, err 75 | } 76 | 77 | return &session, nil 78 | } 79 | 80 | func (s *JSONStorage) UpdateSession(ctx context.Context, session *Session) error { 81 | filePath := filepath.Join(s.path, session.UID+".json") 82 | file, err := os.Open(filePath) 83 | if err != nil { 84 | return err 85 | } 86 | defer file.Close() 87 | 88 | return json.NewEncoder(file).Encode(session) 89 | } 90 | 91 | func (s *JSONStorage) DeleteSession(ctx context.Context, id string) error { 92 | filePath := filepath.Join(s.path, id+".json") 93 | if err := os.Remove(filePath); err != nil { 94 | return err 95 | } 96 | 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /tool/adb/registry.go: -------------------------------------------------------------------------------- 1 | package adb 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/showntop/llmack/log" 8 | "github.com/showntop/llmack/tool" 9 | ) 10 | 11 | var ( 12 | registry *Registry = NewRegistry() 13 | ) 14 | 15 | // The main service class that manages action registration and execution 16 | type Registry struct { 17 | Tools map[string]*tool.Tool 18 | } 19 | 20 | func NewRegistry() *Registry { 21 | return &Registry{ 22 | Tools: make(map[string]*tool.Tool), 23 | } 24 | } 25 | 26 | // RegisterTool should be called after registry initialization 27 | // registry.Tool("click_element_by_index", ClickElementFunc, "click action", paramModel, domains, pageFilter) 28 | func RegisterTool[T, D any]( 29 | r *Registry, 30 | name string, 31 | description string, 32 | function tool.ToolFunc[T, D], 33 | ) error { 34 | 35 | toolx, err := tool.NewWithToolFunc(name, description, function) 36 | if err != nil { 37 | return err 38 | } 39 | r.Tools[name] = toolx 40 | tool.Register(toolx) 41 | log.InfoContextf(context.Background(), "注册工具 name: %s description: %s", name, description) 42 | return nil 43 | } 44 | 45 | func RegisterGlobalTool[T, D any](name string, description string, function tool.ToolFunc[T, D]) error { 46 | return RegisterTool(registry, name, description, function) 47 | } 48 | 49 | // ExecuteTool a registered action 50 | // TODO(LOW): support Context 51 | func (r *Registry) ExecuteTool( 52 | ctx context.Context, 53 | toolName string, 54 | arguments string, 55 | sensitiveData map[string]string, 56 | ) (string, error) { 57 | tool, ok := r.Tools[toolName] 58 | if !ok { 59 | return "", errors.New("tool not found") 60 | } 61 | 62 | result, err := tool.Invoke(ctx, arguments) 63 | if err != nil { 64 | return "", err 65 | } 66 | 67 | return result, nil 68 | } 69 | 70 | func (r *Registry) AvailableTools(includeTools []string) []any { 71 | // Create model from registered actions, used by LLM APIs that support tool calling 72 | 73 | // Filter tools based on includeTools if provided: 74 | // if includeTools is nil, only include tools with no filters 75 | // if includeTools is provided, only include tools that match the includeTools 76 | 77 | availableTools := make([]any, 0) 78 | for name := range r.Tools { 79 | availableTools = append(availableTools, name) 80 | } 81 | 82 | return availableTools 83 | } 84 | -------------------------------------------------------------------------------- /workflow/node.go: -------------------------------------------------------------------------------- 1 | package workflow 2 | 3 | // NodeKind TODO 4 | type NodeKind string 5 | 6 | const ( 7 | // NodeKindTool TODO 8 | NodeKindTool NodeKind = "tool" // ref = 9 | // NodeKindExpr TODO 10 | NodeKindExpr NodeKind = "expr" // ref = 11 | // NodeKindLLM TODO 12 | NodeKindLLM NodeKind = "llm" // ref = 13 | // NodeKindGateway TODO 14 | NodeKindGateway NodeKind = "gateway" // ref = join|fork|excl|incl 15 | // NodeKindIterator TODO 16 | NodeKindIterator NodeKind = "iterator" // ref = 17 | // NodeKindError TODO 18 | NodeKindError NodeKind = "error" // no ref 19 | // NodeKindTermination TODO 20 | NodeKindTermination NodeKind = "termination" // no ref 21 | // NodeKindPrompt TODO 22 | NodeKindPrompt NodeKind = "prompt" // ref = 23 | // NodeKindDelay TODO 24 | NodeKindDelay NodeKind = "delay" // no ref 25 | // NodeKindWait TODO 26 | NodeKindWait NodeKind = "wait" // no ref 27 | // NodeKindVisual TODO 28 | NodeKindVisual NodeKind = "visual" // ref = <*> 29 | // NodeKindDebug TODO 30 | NodeKindDebug NodeKind = "debug" // ref = <*> 31 | // NodeKindBreak TODO 32 | NodeKindBreak NodeKind = "break" // ref = <*> 33 | // NodeKindContinue TODO 34 | NodeKindContinue NodeKind = "continue" // ref = <*> 35 | // NodeKindStart TODO 36 | NodeKindStart NodeKind = "start" // start event 37 | // NodeKindEnd TODO 38 | NodeKindEnd NodeKind = "end" // end event 39 | // NodeKindHuman TODO 40 | NodeKindHuman NodeKind = "human" // user involved in node 41 | ) 42 | 43 | // Node ... 44 | type Node struct { 45 | ID string `json:"id"` // 节点ID 46 | Name string `json:"name"` // 节点名称 support expr @TODO 47 | Description string `json:"description"` // 节点描述 48 | Kind NodeKind `json:"kind"` // 节点类型 49 | Subref string `json:"subref"` // reference to function or subprocess (gateway) 50 | // set of expressions to evaluate, test or pass to function 51 | Inputs Parameters `json:"inputs"` 52 | Outputs Parameters `json:"outputs"` 53 | 54 | // Events []Event `json:"events2"` // 边界事件 55 | // Callbacks []*Action `json:"events"` // 普通事件 56 | // for business configure 57 | // its free now, need tobe constraint in the future 58 | Metadata map[string]any `json:"metadata"` 59 | } 60 | 61 | type NodeID struct { 62 | ID string 63 | } 64 | -------------------------------------------------------------------------------- /prompt/templates/example.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | // ExampleGenerate 样本生成提示词 4 | const ExampleGenerate = ` 5 | You are an expert example selector who can help in selection of right in-context examples to help the most suitable agent solve this problem. 6 | You are also given the prompt instruction which is used to solve this task 7 | [Prompt]: {{prompt}} 8 | You are given the task description of the task: 9 | [Task Description]: {{task_description}} 10 | I'm trying to write a few shots prompt using {{num_examples}} in-context examples to effectively solve any questions of the above task. 11 | Think of analysing, understanding and creating examples of task on the criteria of diversity of types of examples, complexity of the nature/characteristics of the examples and relevance/compatibility to the whole example set in total. 12 | Output all the suggestions/ improvement which could be made to improve each individual example of the whole example selection set. 13 | ` 14 | 15 | // ExampleOptimization 样本优化提示词 16 | const ExampleOptimization = ` 17 | You are an expert example selector who can help in selection of right in-context examples to help the agent solve this problem. 18 | You are also given the prompt instruction which is used to solve this task 19 | [Prompt]: {prompt} 20 | You are given the description of the task: 21 | [Task Description]: {task_description} 22 | I'm trying to write a few shots prompt using {num_examples} in-context examples to effectively solve any questions of the above task. 23 | My current {num_examples} in-context examples set are: {examples} 24 | You are also given a set of suggestions/improvements which could be made to improve each individual example of the whole example selection set: 25 | [SUGGESTION/IMPROVEMENT]: {critique} 26 | Based on the above information, use all of it smartly and diligently to carefully create new set of {num_examples}, which follow these suggestion and improvements. 27 | Make sure to output each example wrapped with and . 28 | 29 | New examples should follow this format strictly: 30 | 31 | [Question] followed by question part of the example 32 | [Answer] followed by the all the steps of logic reasoning statements related to answer. The final answer as "[answer]" 33 | 34 | For Example: 35 | {gt_example} 36 | 37 | 38 | [New Examples]: 39 | ` 40 | -------------------------------------------------------------------------------- /tool/wikipedia/wikipedia.go: -------------------------------------------------------------------------------- 1 | package wikipedia 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/showntop/llmack/tool" 9 | gowiki "github.com/trietmn/go-wiki" 10 | ) 11 | 12 | func init() { 13 | t := tool.New( 14 | tool.WithName("wikipedia_search"), 15 | tool.WithKind("code"), 16 | tool.WithDescription("A tool for performing a Wikipedia search and extracting snippets and webpages. Input should be a search query."), 17 | tool.WithParameters( 18 | tool.Parameter{ 19 | Name: "query", Type: tool.String, Required: true, LLMDescrition: `key words for searching, this should be in the language of "language" parameter`, 20 | }, 21 | tool.Parameter{ 22 | Name: "language", Type: tool.String, Required: true, LLMDescrition: ` 23 | language of the wikipedia to be searched, 24 | only "de" for German, 25 | "en" for English, 26 | "fr" for French, 27 | "hi" for Hindi, 28 | "ja" for Japanese, 29 | "ko" for Korean, 30 | "pl" for Polish, 31 | "pt" for Portuguese, 32 | "ro" for Romanian, 33 | "uk" for Ukrainian, 34 | "vi" for Vietnamese, 35 | and "zh" for Chinese are supported 36 | `, 37 | Options: []string{"de", "en", "fr", "hi", "ja", "ko", "pl", "pt", "ro", "uk", "vi", "zh"}, 38 | }, 39 | ), 40 | tool.WithFunction(Invoke), 41 | ) 42 | tool.Register(t) 43 | } 44 | 45 | // Invoke ... 46 | func Invoke(ctx context.Context, args string) (string, error) { 47 | var params struct { 48 | Query string `json:"query"` 49 | Language string `json:"language"` 50 | } 51 | if err := json.Unmarshal([]byte(args), ¶ms); err != nil { 52 | return "", err 53 | } 54 | 55 | if params.Query == "" { 56 | return "Please input query", fmt.Errorf("query is empty") 57 | } 58 | 59 | // Search for the Wikipedia page title 60 | searchResult, _, err := gowiki.Search(params.Query, 3, false) 61 | if err != nil { 62 | return "", fmt.Errorf("search failed: %v", err) 63 | } 64 | 65 | if len(searchResult) == 0 { 66 | return "No search results found", nil 67 | } 68 | 69 | // Get the first page 70 | page, err := gowiki.GetPage(searchResult[0], -1, false, true) 71 | if err != nil { 72 | return "", fmt.Errorf("failed to get page: %v", err) 73 | } 74 | 75 | // Get the content of the page 76 | content, err := page.GetContent() 77 | if err != nil { 78 | return "", fmt.Errorf("failed to get content: %v", err) 79 | } 80 | 81 | return content, nil 82 | } 83 | -------------------------------------------------------------------------------- /rag/deepdoc/pdf.go: -------------------------------------------------------------------------------- 1 | package deepdoc 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/showntop/llmack/pkg/strings" 9 | 10 | "github.com/dslipak/pdf" 11 | "github.com/pdfcpu/pdfcpu/pkg/api" 12 | 13 | "github.com/showntop/unipdf/extractor" 14 | "github.com/showntop/unipdf/model" 15 | ) 16 | 17 | // PdfDocument ... 18 | type PdfDocument struct { 19 | } 20 | 21 | // Pdf ... 22 | func Pdf() *PdfDocument { 23 | return &PdfDocument{} 24 | } 25 | 26 | // Extract ... 27 | func (d *PdfDocument) Extract(filename string, binary []byte) ([]string, error) { 28 | sections := make([]string, 0) 29 | 30 | reader, err := model.NewPdfReader(bytes.NewReader(binary)) 31 | if err != nil { 32 | return nil, err 33 | } 34 | num, _ := reader.GetNumPages() 35 | for i := 1; i <= num; i++ { 36 | page, _ := reader.GetPage(i) 37 | ext, _ := extractor.New(page) 38 | text, _ := ext.ExtractText() 39 | sections = append(sections, text) 40 | } 41 | 42 | return sections, nil 43 | } 44 | 45 | // Extract ... 46 | func (d *PdfDocument) Extract3(filename string, binary []byte) ([]string, error) { 47 | sections := make([]string, 0) 48 | ctx, err := api.ReadContextFile("./pkg/deepdoc/example/销售认证-销售代表-学习指引.pdf") 49 | // ctx, err := api.ReadContext(bytes.NewReader(binary), model.NewDefaultConfiguration()) 50 | // pdf2.ExtractContentFile("in.pdf", "outDir", nil, nil) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | page, _ := api.ExtractPage(ctx, 1) 55 | var p []byte = make([]byte, 100000) 56 | fmt.Println(page.Read(p)) 57 | 58 | fmt.Println(string(p)) 59 | 60 | api.ExtractContentFile("./pkg/deepdoc/example/销售认证-销售代表-学习指引.pdf", "./", []string{"1"}, nil) 61 | // fmt.Println() 62 | // api.ExtractContent() 63 | // p, _ := ctx.Pages() 64 | // _ = p 65 | // fmt.Println(p.PDFString()) 66 | return sections, nil 67 | } 68 | 69 | func (d *PdfDocument) Extract2(filename string, binary []byte) ([]string, error) { 70 | sections := make([]string, 0) 71 | reader, err := pdf.NewReader(bytes.NewReader(binary), int64(len(binary))) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | for i := 1; i <= reader.NumPage(); i++ { 77 | page := reader.Page(i) 78 | for _, txt := range page.Content().Text { 79 | // fmt.Println(txt.Font, txt.S) 80 | _ = txt 81 | } 82 | // content, _ := reader.Page(i).GetPlainText(nil) 83 | content, _ := page.GetPlainText(nil) 84 | sections = append(sections, strings.TrimSpecial(content)) 85 | } 86 | return sections, nil 87 | } 88 | --------------------------------------------------------------------------------