本文详解如何在 LangChain 表达式语言(LCEL)链中安全传递字符串型文档上下文,解决 TypeError: Expected a Runnable, callable or dict 错误,并提供可复用的结构化链构建方案。
本文详解如何在 langchain 表达式语言(lcel)链中安全传递字符串型文档上下文,解决 `typeerror: expected a runnable, callable or dict` 错误,并提供可复用的结构化链构建方案。
在 LangChain 的 LCEL(LangChain Expression Language)中,链式操作符 | 要求每个中间节点必须是 可运行对象(Runnable) ——例如 RunnablePassthrough、PromptTemplate、LLM 或 RunnableParallel,而不能直接传入原始 Python 值(如 str、list)。你遇到的错误:
TypeError: Expected a Runnable, callable or dict. Instead got an unsupported type: <class></class>
正是因为在链定义中直接将字符串 context 作为字典值写入:
{"context": context, "question": RunnablePassthrough()} # ❌ context 是 str,非 Runnable
LCEL 尝试将其自动转为 RunnableParallel,但 coerce_to_runnable("some string") 不支持字符串类型,从而抛出异常。
✅ 正确做法:分离“数据注入”与“链定义”
LCEL 链应声明式定义结构,而具体数据在 .invoke() 时传入。你需要:
- 将 context 和 question 都设为 RunnablePassthrough() 占位;
- 在调用时以字典形式传入实际值。
✅ 修正后的完整链定义与调用示例:
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# 1. 定义提示模板(推荐使用 ChatPromptTemplate,更健壮)
template = """Use the following pieces of context to answer the question at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
Use three sentences maximum and keep the answer as concise as possible.
Always say "thanks for asking!" at the end of the answer.
{context}
Question: {question}
Helpful Answer:"""
prompt = ChatPromptTemplate.from_template(template)
# 2. 构建纯 Runnable 链(所有键对应 Runnable,非原始值!)
chain = (
{"context": RunnablePassthrough(), "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 3. 在 invoke 时传入真实数据(context 是 str,question 是用户 query)
context_str = "\n".join([doc.page_content for doc, score in result_context])
inference = chain.invoke({
"context": context_str,
"question": query
})
print(inference) # 输出模型生成的回答
? 为什么这样设计?——LCEL 的核心原则
- 声明式(Declarative)优先:链描述 数据流逻辑,而非执行细节;
- 可组合性(Composability):RunnablePassthrough() 允许你在任意位置“透传”输入字段,便于后续接入检索器、重排序器等组件;
- 类型安全:coerce_to_runnable() 强制校验每一步的可运行性,避免隐式错误。
⚠️ 注意事项与进阶建议
不要在链内做字符串拼接:像 "\n".join(...) 这类操作应在 .invoke() 前完成,或封装为 RunnableLambda(见下文);
-
若需动态构造 context(如带元数据/评分),可封装为轻量函数:
from langchain_core.runnables import RunnableLambda def format_context(inputs): docs_with_scores = inputs["retrieved_docs"] # 假设输入含此字段 return "\n---\n".join([ f"[Score: {score:.3f}]\n{doc.page_content}" for doc, score in docs_with_scores ]) chain = ( {"retrieved_docs": RunnablePassthrough(), "question": RunnablePassthrough()} | {"context": RunnableLambda(format_context), "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) 推荐整合检索流程:若你已实现多路检索 + RRF 重排,建议将其封装为一个 Runnable(如继承 Runnable 或用 RunnableLambda),使整个 RAG 流程端到端可链式编排,提升可维护性与复用性。
✅ 总结
| 错误模式 | 正确模式 |
|---|---|
| 在链定义中直接传 str 值(如 {"context": "..."}) | 链中仅用 RunnablePassthrough() 占位,.invoke() 时传 dict |
| 混淆“链结构”与“运行时数据” | 明确区分:链是蓝图,invoke() 是执行 |
遵循这一范式,你不仅能解决当前报错,还能为后续集成向量检索、路由分支、监控日志等高级能力打下坚实基础。










