langchainrust:Rust 版 LangChain 框架(LLM+Agent+RAG)

langchainrust:Rust 版 LangChain 框架(LLM+Agent+RAG)

langchainrust 是一个 Rust 实现的 LLM 应用框架,提供 LLM 调用、Agent、RAG、向量存储、回调追踪等功能。

核心组件一览

组件 说明
LLM OpenAI 兼容接口,支持流式输出、function calling
Agents ReActAgent 智能代理,支持工具调用和多步推理
Prompts 提示词模板,支持变量替换
Memory 对话历史管理,支持多种记忆策略
Chains 链式调用,支持单步和多步流水线
RAG 文档分割、向量存储、语义检索的完整流程
Loaders PDF 和 CSV 文档加载器
Tools 内置工具:计算器、日期、数学运算、URL抓取
Callbacks 执行追踪和 LangSmith 集成
Tool Calling bind_tools() 工具绑定和结构化输出

安装

Cargo.toml:

toml 复制代码
[dependencies]
langchainrust = "0.2.3"
tokio = { version = "1.0", features = ["full"] }

启用 Qdrant 向量数据库(可选):

toml 复制代码
langchainrust = { version = "0.2.3", features = ["qdrant-integration"] }

一、LLM 调用

基础用法

创建 OpenAI 客户端并发起对话:

rust 复制代码
use langchainrust::{OpenAIChat, OpenAIConfig, BaseChatModel};
use langchainrust::schema::Message;

let config = OpenAIConfig {
    api_key: std::env::var("OPENAI_API_KEY")?,
    base_url: "https://api.openai.com/v1".to_string(),
    model: "gpt-3.5-turbo".to_string(),
    streaming: false,
    temperature: Some(0.7),
    max_tokens: Some(500),
    ..Default::default()
};

let llm = OpenAIChat::new(config);

let messages = vec![
    Message::system("你是一个助手"),
    Message::human("什么是 Rust 的所有权机制?"),
];

let response = llm.chat(messages, None).await?;
println!("{}", response.content);

说明:

  • Message::system() 设置系统提示,定义 AI 的角色和行为
  • Message::human() 用户消息
  • Message::ai() AI 响应消息(用于多轮对话时添加历史)
  • temperature 控制随机性,0.0 最确定,2.0 最随机

流式输出

逐 token 输出,适合长文本生成:

rust 复制代码
use futures_util::StreamExt;

let streaming_config = OpenAIConfig {
    streaming: true,
    ..Default::default()
};

let llm = OpenAIChat::new(streaming_config);
let stream = llm.stream(messages, None).await?;

while let Some(token) = stream.next().await {
    match token {
        Ok(text) => print!("{}", text),  // 实时打印
        Err(e) => eprintln!("Error: {}", e),
    }
}

使用其他模型

框架支持 OpenAI 兼容接口,可以对接 Qwen、DeepSeek、Azure 等:

rust 复制代码
// 通义千问
let config = OpenAIConfig {
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1".to_string(),
    model: "qwen-turbo".to_string(),
    api_key: std::env::var("DASHSCOPE_API_KEY")?,
    ..Default::default()
};

// DeepSeek
let config = OpenAIConfig {
    base_url: "https://api.deepseek.com/v1".to_string(),
    model: "deepseek-chat".to_string(),
    ..Default::default()
};

// 国内代理
let config = OpenAIConfig {
    base_url: "https://api.openai-proxy.com/v1".to_string(),
    ..Default::default()
};

配置项详解

字段 类型 说明
api_key String API 密钥,从环境变量读取
base_url String API 端点,默认 OpenAI,可换成其他兼容服务
model String 模型名称,如 gpt-3.5-turbo, gpt-4
streaming bool 是否流式输出
temperature Option 采样温度,0.0-2.0
max_tokens Option 最大生成 token 数,控制成本

二、提示词模板

PromptTemplate - 字符串模板

避免在代码里硬编码提示词,使用模板管理:

rust 复制代码
use langchainrust::prompts::PromptTemplate;
use std::collections::HashMap;

let template = PromptTemplate::new("你好,{name}!今天是{day}。");

let mut vars = HashMap::new();
vars.insert("name", "小明");
vars.insert("day", "星期一");

let prompt = template.format(&vars)?;
// 输出: "你好,小明!今天是星期一。"

说明 : 用 {变量名} 定义占位符,调用时传入值替换。

ChatPromptTemplate - 聊天模板

管理多角色对话的模板:

rust 复制代码
use langchainrust::prompts::ChatPromptTemplate;
use langchainrust::schema::Message;

let template = ChatPromptTemplate::new(vec![
    Message::system("你是一个{role},专精于{domain}。"),
    Message::human("你好,我是{name}。"),
    Message::human("{question}"),
]);

let mut vars = HashMap::new();
vars.insert("role", "Rust 专家");
vars.insert("domain", "系统编程");
vars.insert("name", "小红");
vars.insert("question", "解释所有权机制");

let messages = template.format(&vars)?;
// 直接传给 llm.chat()
let response = llm.chat(messages, None).await?;

说明: 模板中的每个 Message 都支持变量替换,适合构建可复用的对话模板。


三、Agent 与工具调用

Agent 能自主调用工具完成复杂任务。

基础 Agent 用法

rust 复制代码
use langchainrust::{
    ReActAgent, AgentExecutor, BaseAgent, BaseTool,
    Calculator, DateTimeTool, SimpleMathTool,
};
use std::sync::Arc;

// 注册工具
let tools: Vec<Arc<dyn BaseTool>> = vec![
    Arc::new(Calculator::new()),
    Arc::new(DateTimeTool::new()),
    Arc::new(SimpleMathTool::new()),
];

// 创建 Agent
let agent = ReActAgent::new(llm, tools.clone(), None);

// 创建执行器,设置最大迭代次数防止无限循环
let executor = AgentExecutor::new(
    Arc::new(agent) as Arc<dyn BaseAgent>,
    tools
).with_max_iterations(5);

// 执行任务
let result = executor.invoke("计算 37 * 48,告诉我今天是星期几".to_string()).await?;
println!("结果: {}", result);

说明:

  • Agent 会自动分解任务,调用合适的工具
  • max_iterations 防止 Agent 卡在循环里
  • ReActAgent 采用 Reasoning + Acting 模式,先思考再行动

Agent 执行过程示例

用户输入: "计算 37 * 48"

Agent 内部过程(自动完成):

yaml 复制代码
Thought: 需要计算乘法
Action: Calculator
Input: 37 * 48
Observation: 1776
Thought: 我有答案了
Final Answer: 37 * 48 = 1776

自定义工具

实现 BaseTool trait 创建自己的工具:

rust 复制代码
use langchainrust::{BaseTool, ToolDefinition, ToolResult};
use async_trait::async_trait;

pub struct WeatherTool;

#[async_trait]
impl BaseTool for WeatherTool {
    fn name(&self) -> &str {
        "weather"
    }
    
    fn description(&self) -> &str {
        "查询指定城市的天气。输入城市名称,返回天气信息。"
    }
    
    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: self.name().to_string(),
            description: self.description().to_string(),
            parameters: serde_json::json!({
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如北京、上海"
                    }
                },
                "required": ["city"]
            }),
        }
    }
    
    async fn call(&self, input: serde_json::Value) -> ToolResult {
        let city = input["city"].as_str().unwrap_or("unknown");
        
        // 调用真实天气 API
        let weather = fetch_weather(city).await?;
        
        Ok(serde_json::json!({
            "city": city,
            "temperature": weather.temp,
            "condition": weather.condition
        }))
    }
}

说明:

  • namedescription 会被 Agent 看到,决定是否调用
  • parameters 定义输入参数的 schema,支持 JSON Schema 格式
  • call 是实际执行逻辑

内置工具列表

工具 说明
Calculator 数学表达式计算
DateTimeTool 获取当前日期时间
SimpleMathTool sin, cos, sqrt, factorial 等
URLFetchTool 抓取网页内容

四、Tool Calling

v0.2.3 支持 OpenAI 的 function calling,让 LLM 直接调用预定义函数。

bind_tools() 绑定工具

rust 复制代码
use langchainrust::ToolDefinition;

let tools = vec![
    ToolDefinition {
        name: "get_user_info".to_string(),
        description: "查询用户信息".to_string(),
        parameters: serde_json::json!({
            "type": "object",
            "properties": {
                "user_id": {
                    "type": "string",
                    "description": "用户 ID"
                }
            },
            "required": ["user_id"]
        }),
    },
];

// 绑定工具到 LLM
let llm_with_tools = llm.bind_tools(tools);

let messages = vec![
    Message::human("查询用户 abc123 的信息"),
];

let response = llm_with_tools.chat(messages, None).await?;

// 检查是否有工具调用请求
if let Some(tool_calls) = response.tool_calls {
    for call in tool_calls {
        println!("工具: {}", call.name);        // "get_user_info"
        println!("参数: {}", call.arguments);   // {"user_id": "abc123"}
        
        // 执行工具逻辑
        let result = execute_tool(&call.name, call.arguments)?;
        
        // 将结果返回给 LLM 继续对话
        let follow_up = vec![
            Message::human("查询用户 abc123 的信息"),
            Message::tool_call(call.id.clone(), call.name.clone(), call.arguments),
            Message::tool_result(call.id.clone(), result),
        ];
        
        let final_response = llm_with_tools.chat(follow_up, None).await?;
        println!("{}", final_response.content);
    }
}

说明:

  • bind_tools() 告诉 LLM 有哪些工具可用
  • LLM 返回 tool_calls 表示想调用工具
  • 需要执行工具并把结果通过 Message::tool_result() 返回给 LLM

结构化输出

强制 LLM 返回特定格式的数据:

rust 复制代码
use schemars::schema_for;

#[derive(serde::Deserialize, schemars::JsonSchema)]
struct UserInfo {
    name: String,
    age: u32,
    interests: Vec<String>,
}

// 生成 schema
let schema = schema_for!(UserInfo);

// 配置 LLM 强制返回该格式
let llm_structured = llm.with_structured_output(schema);

let messages = vec![
    Message::human("分析这个用户:张三,25岁,喜欢编程和音乐"),
];

let response = llm_structured.chat(messages, None).await?;

// 直接反序列化
let info: UserInfo = serde_json::from_str(&response.content)?;
println!("姓名: {}", info.name);
println!("年龄: {}", info.age);
println!("兴趣: {:?}", info.interests);

说明 : 用 schemars 生成 JSON Schema,LLM 会严格按照 schema 返回数据。


五、对话记忆

ChatMessageHistory - 历史记录

rust 复制代码
use langchainrust::{ChatMessageHistory, Message};

let mut history = ChatMessageHistory::new();

// 添加消息
history.add_message(Message::human("你好!"));
history.add_message(Message::ai("你好!有什么可以帮你?"));
history.add_message(Message::human("什么是 Rust?"));
history.add_message(Message::ai("Rust 是一门系统编程语言..."));

// 获取所有历史
for msg in history.messages() {
    println!("{:?}: {}", msg.message_type, msg.content);
}

// 清空
history.clear();

ConversationBufferMemory - 缓冲记忆

自动管理对话历史:

rust 复制代码
use langchainrust::{ConversationBufferMemory, Memory};

let memory = ConversationBufferMemory::new();

// 保存对话
memory.save_context(HashMap::from([
    ("input", "什么是所有权?"),
    ("output", "所有权是 Rust 的核心概念,确保内存安全...")
]))?;

// 加载历史
let vars = memory.load_memory_variables()?;
println!("历史: {:?}", vars);

// 用于多轮对话
let messages_with_history = memory.get_history();
let full_messages = messages_with_history.into_iter()
    .chain(vec![Message::human("能否详细解释?")])
    .collect();
let response = llm.chat(full_messages, None).await?;

ConversationBufferWindowMemory - 窗口记忆

只保留最近 K 条,控制上下文长度:

rust 复制代码
use langchainrust::memory::ConversationBufferWindowMemory;

// 只保留最近 5 条消息
let window_memory = ConversationBufferWindowMemory::new(5);

// 旧的消息会自动丢弃
// 适合控制 token 使用量

说明: 窗口记忆适合长对话场景,避免历史太长超出模型限制。


六、Chain 工作流

LLMChain - 单步链

rust 复制代码
use langchainrust::{LLMChain, BaseChain};

let chain = LLMChain::new(llm, "分析以下主题: {topic}");

let mut inputs = HashMap::new();
inputs.insert("topic", "Rust 语言的特点");

let result = chain.invoke(inputs).await?;
println!("{}", result);

SequentialChain - 多步流水线

串联多个 Chain,上一个的输出作为下一个的输入:

rust 复制代码
use langchainrust::{SequentialChain, LLMChain, BaseChain};
use std::sync::Arc;
use serde_json::Value;

// 步骤1: 分析
let chain1 = LLMChain::new(llm1, "分析: {topic}");
let chain1 = Arc::new(chain1);

// 步骤2: 总结
let chain2 = LLMChain::new(llm2, "根据以下分析生成总结: {analysis}");
let chain2 = Arc::new(chain2);

// 步骤3: 建议
let chain3 = LLMChain::new(llm3, "基于以下总结给出行动建议: {summary}");
let chain3 = Arc::new(chain3);

// 组装流水线
let pipeline = SequentialChain::new()
    // chain1: 输入 topic,输出 analysis
    .add_chain(chain1, vec!["topic"], vec!["analysis"])
    // chain2: 输入 analysis,输出 summary
    .add_chain(chain2, vec!["analysis"], vec!["summary"])
    // chain3: 输入 summary,输出 suggestions
    .add_chain(chain3, vec!["summary"], vec!["suggestions"]);

// 执行
let inputs = HashMap::from([
    ("topic", Value::String("Rust 在 AI 领域的应用".to_string()))
]);

let results = pipeline.invoke(inputs).await?;

println!("分析: {}", results["analysis"]);
println!("总结: {}", results["summary"]);
println!("建议: {}", results["suggestions"]);

说明:

  • add_chain 定义输入输出变量名
  • 前一个 chain 的输出变量自动传给下一个
  • 最终返回所有变量的值

七、RAG 检索增强生成

RAG 流程: 加载文档 → 分割 → 向量化 → 存储 → 检索 → 生成回答

文档分割

长文档需要分割成小块:

rust 复制代码
use langchainrust::{
    Document, RecursiveCharacterSplitter, TextSplitter
};

let doc = Document::new("Rust 是一门系统编程语言,专注于安全性...");

// 200 字一块,50 字重叠(防止信息断裂)
let splitter = RecursiveCharacterSplitter::new(200, 50);

let chunks = splitter.split_document(&doc);

for (i, chunk) in chunks.iter().enumerate() {
    println!("片段 {}: {}", i, chunk.content);
}

说明:

  • chunk_size 每块最大长度
  • chunk_overlap 相邻块重叠长度,避免句子被切断

向量存储与检索

rust 复制代码
use langchainrust::{
    InMemoryVectorStore, OpenAIEmbeddings, SimilarityRetriever,
    RetrieverTrait, Document
};
use std::sync::Arc;

// 创建 Embeddings(生成向量)
let embeddings = Arc::new(OpenAIEmbeddings::new(OpenAIEmbeddingsConfig::default()));

// 创建向量存储
let store = Arc::new(InMemoryVectorStore::new());

// 创建检索器
let retriever = SimilarityRetriever::new(store.clone(), embeddings);

// 添加文档(自动生成向量并存储)
let docs = vec![
    Document::new("Rust 由 Mozilla 研发,2015 年发布 1.0。"),
    Document::new("所有权是 Rust 的核心特性,确保内存安全。"),
    Document::new("Cargo 是 Rust 的构建系统和包管理器。"),
];

retriever.add_documents(docs).await?;

// 检索
let question = "Rust 什么时候发布的?";
let results = retriever.retrieve(question, 3).await?;

for result in results {
    println!("相关度: {}", result.score);
    println!("内容: {}", result.document.content);
}

完整 RAG 流程

rust 复制代码
async fn rag_chat(question: &str) -> Result<String, Box<dyn std::error::Error>> {
    // 1. 检索相关文档
    let results = retriever.retrieve(question, 3).await?;
    
    // 2. 构建上下文
    let context = results.iter()
        .map(|r| r.document.content.as_str())
        .collect::<Vec<_>>()
        .join("\n");
    
    // 3. 构建提示词
    let messages = vec![
        Message::system("根据上下文回答问题。如果上下文没有相关信息,说明不知道。"),
        Message::human(&format!(
            "上下文:\n{}\n\n问题: {}", 
            context, question
        )),
    ];
    
    // 4. LLM 生成回答
    let response = llm.chat(messages, None).await?;
    
    Ok(response.content)
}

let answer = rag_chat("什么是所有权?").await?;
println!("{}", answer);

八、向量数据库

InMemoryVectorStore

内存存储,适合开发测试:

rust 复制代码
use langchainrust::vector_stores::InMemoryVectorStore;

let store = InMemoryVectorStore::new();

特点:

  • 无需配置,直接使用
  • 极快查询速度
  • 数据不持久化
  • 适合测试和小规模数据

QdrantVectorStore

生产级向量数据库:

rust 复制代码
use langchainrust::vector_stores::{
    QdrantVectorStore, QdrantConfig, QdrantDistance
};

let config = QdrantConfig::new("http://localhost:6334", "knowledge_base")
    .with_vector_size(1536)           // OpenAI embedding 维度
    .with_distance(QdrantDistance::Cosine);

let store = QdrantVectorStore::new(config).await?;

特点:

  • 支持百万级向量
  • 数据持久化
  • 支持分布式部署
  • 生产环境首选

启动 Qdrant:

bash 复制代码
docker run -p 6333:6333 -p 6334:6334 \
    -v $(pwd)/qdrant_storage:/qdrant/storage \
    qdrant/qdrant

两种存储对比

特性 InMemory Qdrant
持久化
容量 内存限制 百万级
配置 无需 需启动服务
适用场景 测试/原型 生产

九、文档加载器

PDF 加载

rust 复制代码
use langchainrust::retrieval::{PDFLoader, DocumentLoader};

let loader = PDFLoader::new("docs/manual.pdf");
let documents = loader.load().await?;

for doc in documents {
    println!("页 {}: {}", doc.metadata["page"], doc.content);
}

CSV 加载

rust 复制代码
use langchainrust::retrieval::{CSVLoader, DocumentLoader};

// 指定内容列
let loader = CSVLoader::new("data/products.csv", "description");
let documents = loader.load().await?;

for doc in documents {
    println!("产品: {}", doc.content);
    println!("行数据: {:?}", doc.metadata);
}

十、Callbacks 回调系统

追踪执行过程,集成 LangSmith。

StdOutHandler - 控制台输出

rust 复制代码
use langchainrust::callbacks::{CallbackManager, StdOutHandler};

let callback_manager = CallbackManager::new()
    .add_handler(StdOutHandler::new());

let llm = OpenAIChat::new(config)
    .with_callbacks(callback_manager);

// 执行时会打印:
// [LLM Start] Calling gpt-3.5-turbo
// [LLM End] Response received
// [Tool Start] Calculator
// [Tool End] Result: 1776

LangSmith 集成

LangSmith 是 LangChain 官方的追踪平台:

rust 复制代码
use langchainrust::callbacks::{LangSmithHandler, LangSmithConfig};

let config = LangSmithConfig {
    api_key: std::env::var("LANGCHAIN_API_KEY")?,
    project_name: "my-project".to_string(),
    endpoint: "https://api.smith.langchain.com".to_string(),
};

let handler = LangSmithHandler::new(config);
let callback_manager = CallbackManager::new().add_handler(handler);

let llm = OpenAIChat::new(llm_config)
    .with_callbacks(callback_manager);

执行轨迹会自动上传到 LangSmith,可以查看:

  • 完整调用链路
  • Token 使用统计
  • 每步耗时
  • 错误追踪

自定义回调处理器

rust 复制代码
use langchainrust::callbacks::{BaseCallbackHandler, CallbackEvent};
use async_trait::async_trait;

pub struct MetricsHandler {
    call_count: AtomicU64,
}

#[async_trait]
impl BaseCallbackHandler for MetricsHandler {
    async fn on_llm_start(&self, event: CallbackEvent) {
        self.call_count.fetch_add(1, Ordering::SeqCst);
        println!("LLM 调用开始,累计: {}", self.call_count.load(Ordering::SeqCst));
    }
    
    async fn on_llm_end(&self, event: CallbackEvent) {
        println!("LLM 调用结束");
    }
    
    async fn on_tool_start(&self, event: CallbackEvent) {
        println!("工具调用开始: {}", event.tool_name);
    }
}

十一、项目结构

bash 复制代码
src/
├── core/                    # 核心 trait 定义
│   ├── language_models/     # BaseChatModel trait
│   ├── runnables/           # Runnable trait
│   └── tools/               # BaseTool trait
│
├── language_models/         # LLM 实现
│   └── openai/              # OpenAI 客户端
│
├── embeddings/              # 文本嵌入
│   ├── openai.rs            # OpenAI Embeddings
│   └── mock.rs              # Mock(测试用)
│
├── vector_stores/           # 向量数据库
│   ├── memory.rs            # 内存存储
│   └── qdrant.rs            # Qdrant
│
├── retrieval/               # RAG 组件
│   ├── retriever.rs         # 检索器
│   ├── splitter.rs          # 文档分割
│   └── loaders/             # 文档加载器
│       ├── pdf.rs
│       └── csv.rs
│
├── agents/                  # Agent 框架
│   └── react/               # ReActAgent
│
├── prompts/                 # 提示词模板
│
├── memory/                  # 对话记忆
│
├── chains/                  # Chain 工作流
│
├── tools/                   # 内置工具
│
├── callbacks/               # 回调系统
│
└── schema/                  # 数据结构定义

十二、测试

运行测试

bash 复制代码
# 单元测试(不需要 API Key)
cargo test --lib

# 显示输出
cargo test -- --nocapture

# 运行特定测试
cargo test test_prompt_template -- --nocapture

# 集成测试(需要 API Key)
export OPENAI_API_KEY="your-key"
cargo test --test integration_agent

# Qdrant 相关测试
cargo test --features qdrant-integration

测试分类

类型 目录 特点
单元测试 tests/unit/ 无需 API Key
集成测试 tests/integration/ 需要 API Key
E2E 测试 tests/e2e/ 完整流程

十三、常见场景示例

场景1: 多轮对话

rust 复制代码
let mut history = ChatMessageHistory::new();

loop {
    let user_input = read_user_input();
    
    // 构建完整历史
    let messages: Vec<Message> = history.messages()
        .into_iter()
        .chain(vec![Message::human(&user_input)])
        .collect();
    
    let response = llm.chat(messages, None).await?;
    
    // 保存历史
    history.add_message(Message::human(&user_input));
    history.add_message(Message::ai(&response.content));
    
    println!("AI: {}", response.content);
}

场景2: 知识库问答

rust 复制代码
// 启动时构建知识库
let knowledge_docs = load_documents("knowledge/");
retriever.add_documents(knowledge_docs).await?;

// 查询时
let question = "如何配置环境变量?";
let results = retriever.retrieve(question, 5).await?;
let context = build_context(&results);
let answer = llm.chat(build_rag_prompt(context, question), None).await?;

场景3: Agent 自动处理

rust 复制代码
let executor = AgentExecutor::new(agent, tools)
    .with_max_iterations(10);

// Agent 自动决定调用哪些工具
let result = executor.invoke("帮我分析这篇文章,计算统计数据").await?;

十四、环境配置

bash 复制代码
# 必须配置
export OPENAI_API_KEY="your-api-key"

# 可选配置
export OPENAI_BASE_URL="https://api.openai.com/v1"

# LangSmith 追踪
export LANGCHAIN_API_KEY="your-langsmith-key"
export LANGCHAIN_PROJECT="my-project"

资源链接

运行示例:

bash 复制代码
cargo run --example hello_llm
cargo run --example agent_with_tools
cargo run --example rag_demo
相关推荐
嘉伟桑1 小时前
Python调用电价API返回JSON示例:分时电价、现货电价和字段解析
后端
武子康1 小时前
Shippy:确定性工具、会话级 Sandbox 与 Live-Data Eval(4 类收敛 + 7 步实现方案 + 6 类评测指标)
前端·人工智能·后端
我叫黑大帅2 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
AskHarries2 小时前
文件上传系统
后端
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530143 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908403 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端