package main import ( "bufio" "bytes" "context" "encoding/json" "fmt" "io" "net/http" "os" "strings" "time" ) // This program demonstrates a simple streaming client that posts a chat/completions // request with `stream: true` and prints chunks as they arrive. It avoids using the // SDK so it's easier to adapt to providers that expose an OpenAI-compatible // streaming chat/completions endpoint (for example: https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions). func main() { apiKey := os.Getenv("DASHSCOPE_API_KEY") if apiKey == "" { fmt.Fprintln(os.Stderr, "DASHSCOPE_API_KEY is not set") os.Exit(2) } // Build request body for chat/completions streaming. // Adjust model/messages according to the provider's expectations. body := map[string]any{ "model": "qwen-plus", "messages": []map[string]string{ {"role": "user", "content": "你是谁"}, }, "stream": true, } b, _ := json.Marshal(body) // Use the chat/completions endpoint which commonly supports streaming. url := "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions" req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, bytes.NewReader(b)) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+apiKey) req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: 0} resp, err := client.Do(req) if err != nil { panic(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { // Read body for debugging (limit size to avoid huge dumps) data, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) panic(fmt.Errorf("request failed: %s - %s", resp.Status, string(data))) } // Read streaming response progressively. Many OpenAI-compatible servers // use Server-Sent Events (SSE) style streaming where each event line is // prefixed with "data: " and terminated by a blank line. reader := bufio.NewReader(resp.Body) // Set a small context deadline so a stalled stream doesn't hang forever. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() // Read loop: handle lines like "data: {json}\n" or raw JSON chunks. for { select { case <-ctx.Done(): fmt.Fprintln(os.Stderr, "stream timeout") return default: } line, err := reader.ReadString('\n') if err != nil { if err == io.EOF { return } panic(err) } line = strings.TrimSpace(line) if line == "" { // ignore empty lines continue } // SSE-style: "data: [DONE]" or "data: { ... }" if strings.HasPrefix(line, "data:") { payload := strings.TrimSpace(strings.TrimPrefix(line, "data:")) if payload == "[DONE]" { // stream finished return } // Try to decode JSON and extract text fields commonly used in streams. var obj map[string]any if err := json.Unmarshal([]byte(payload), &obj); err == nil { // Try common paths: choices[0].delta.content or choices[0].text if choices, ok := obj["choices"].([]any); ok && len(choices) > 0 { if choiceMap, ok := choices[0].(map[string]any); ok { // delta.content if delta, ok := choiceMap["delta"].(map[string]any); ok { if content, ok := delta["content"].(string); ok { fmt.Print(content) continue } } // text if text, ok := choiceMap["text"].(string); ok { fmt.Print(text) continue } } } // Fallback: print the raw JSON payload fmt.Println(payload) } else { // Not JSON, print raw payload fmt.Println(payload) } continue } // Some providers may stream raw JSON chunks (not prefixed with data:) // Try to handle those as well: attempt to parse the line as JSON and // print fields if present, otherwise print the raw line. var obj map[string]any if err := json.Unmarshal([]byte(line), &obj); err == nil { if choices, ok := obj["choices"].([]any); ok && len(choices) > 0 { if choiceMap, ok := choices[0].(map[string]any); ok { if delta, ok := choiceMap["delta"].(map[string]any); ok { if content, ok := delta["content"].(string); ok { fmt.Print(content) continue } } if text, ok := choiceMap["text"].(string); ok { fmt.Print(text) continue } } } // fallback print full JSON fmt.Println(line) } else { fmt.Println(line) } } }
Github开源生信云平台 DEMO