When Should You Use LangChain or Semantic Kernel vs Simple File-Based AI Agent Approaches?
Problem
I spent three weeks learning LangChain for a simple personal automation project. The result? Over 500 lines of code, confusing abstractions, and an agent that was harder to debug than my original bash script.
The mistake wasn’t LangChain itself. The mistake was using a production-grade framework for a personal productivity tool.
A Reddit discussion in r/AI_Agents crystallized this insight:
“It depends on the usecase. For your own productivity yes. But for creating production level software you need code.” — mohdgame
“Simple markdown and folders just work and more importantly more people can grok it than langchain and python” — IversusAI
I had chosen the wrong tool for my use case. Let me share what I learned about when to use each approach.
The Tension
When building AI agents, you face a fundamental architecture decision:
┌─────────────────────────────────────────────────────────────────────┐│ FRAMEWORK VS FILE-BASED │├─────────────────────────────────────────────────────────────────────┤│ ││ LangChain/Semantic Kernel File-Based (Markdown/Folders) ││ ───────────────────────── ────────────────────────────── ││ ││ + Built-in observability + Maximum transparency ││ + Memory management + Easy debugging ││ + Tool orchestration + Version control friendly ││ + Multi-model support + Non-technical accessible ││ + Error handling patterns + Low learning curve ││ + Production scaling + Fast prototyping ││ ││ - Steep learning curve - Manual state management ││ - Abstraction complexity - Limited built-in features ││ - Debugging through layers - Custom error handling ││ - Team expertise required - Scale challenges ││ │└─────────────────────────────────────────────────────────────────────┘The key insight: Neither approach is better. They serve different purposes.
When I Tried Both Approaches
Let me show you the difference through my actual project. I wanted to build an agent that reads my daily notes and generates a weekly summary.
My File-Based Attempt
I started with a simple folder structure:
my-agent/├── contexts/│ ├── monday.md│ ├── tuesday.md│ └── wednesday.md├── outputs/│ └── weekly-summary.md└── agent.pyThe implementation was straightforward:
from pathlib import Pathfrom anthropic import Anthropic
class FileAgent: def __init__(self, workspace: str): self.workspace = Path(workspace) self.contexts = self.workspace / "contexts" self.outputs = self.workspace / "outputs" self.client = Anthropic()
def read_contexts(self) -> str: """Read all markdown files from contexts folder.""" all_context = [] for file in sorted(self.contexts.glob("*.md")): all_context.append(f"=== {file.stem} ===\n{file.read_text()}") return "\n\n".join(all_context)
def generate_summary(self) -> str: """Generate weekly summary from contexts.""" context = self.read_contexts()
response = self.client.messages.create( model="claude-opus-4-20250514", max_tokens=2000, messages=[{ "role": "user", "content": f"Summarize my week based on these notes:\n\n{context}" }] )
summary = response.content[0].text output_file = self.outputs / "weekly-summary.md" output_file.write_text(summary) return summary
# Usageagent = FileAgent("./my-agent")summary = agent.generate_summary()print(summary)Total: 35 lines of code. I could understand every line. When something broke, I knew exactly where to look.
My LangChain Attempt
Then I thought, “I should make this production-ready.” So I rebuilt it with LangChain:
from langchain.agents import AgentExecutor, create_openai_functions_agentfrom langchain.memory import ConversationBufferMemoryfrom langchain.tools import Toolfrom langchain_openai import ChatOpenAIfrom langchain.callbacks import tracing_v2from langchain_community.document_loaders import DirectoryLoaderfrom langchain.text_splitter import MarkdownHeaderTextSplitterfrom pydantic import BaseModel, Fieldimport logging
# Setup logginglogging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)
class ContextInput(BaseModel): directory: str = Field(description="Directory containing markdown files")
class SummaryInput(BaseModel): contexts: str = Field(description="Combined context from files")
class ProductionAgent: def __init__(self, model_name: str = "gpt-4"): self.llm = ChatOpenAI(model=model_name, temperature=0) self.memory = ConversationBufferMemory( memory_key="chat_history", return_messages=True ) self.tools = self._setup_tools() self.agent = self._create_agent() self.logger = logger
def _setup_tools(self) -> list[Tool]: def load_contexts(directory: str) -> str: loader = DirectoryLoader(directory, glob="**/*.md") docs = loader.load() splitter = MarkdownHeaderTextSplitter() split_docs = [splitter.split_text(doc.page_content) for doc in docs] return "\n".join([str(doc) for sublist in split_docs for doc in sublist])
def generate_summary(contexts: str) -> str: return self.llm.invoke( f"Summarize the following weekly notes:\n\n{contexts}" ).content
return [ Tool( name="load_contexts", func=load_contexts, description="Load all markdown files from a directory", args_schema=ContextInput ), Tool( name="generate_summary", func=generate_summary, description="Generate a summary from combined contexts", args_schema=SummaryInput ) ]
def _create_agent(self) -> AgentExecutor: agent = create_openai_functions_agent( llm=self.llm, tools=self.tools, prompt=self._create_prompt() ) return AgentExecutor( agent=agent, tools=self.tools, memory=self.memory, verbose=True, max_iterations=5, callbacks=[tracing_v2.LangChainTracer()] )
def _create_prompt(self): from langchain.prompts import ChatPromptTemplate return ChatPromptTemplate.from_messages([ ("system", "You are a helpful assistant that summarizes weekly notes."), ("user", "{input}"), ("assistant", "{agent_scratchpad}") ])
async def process(self, directory: str) -> str: try: result = await self.agent.ainvoke({ "input": f"Load contexts from {directory} and generate a summary" }) return result["output"] except Exception as e: self.logger.error(f"Agent failed: {e}") raise
# Usageimport asyncioagent = ProductionAgent()summary = asyncio.run(agent.process("./my-agent/contexts"))print(summary)Total: 95 lines of code. More abstractions, more complexity.
What Went Wrong
The problem wasn’t LangChain. The problem was my use case.
My weekly summary agent has these characteristics:
- Single user (me)
- Runs once per week
- Linear workflow (read files, generate summary)
- No complex state management
- No multi-agent coordination
- No enterprise integrations
For this use case, the file-based approach is superior because:
- Transparency: I can read any
.mdfile and understand the context - Debugging: When the summary is wrong, I check the source files
- Version control: Git shows exactly what changed
- No learning curve: Anyone can understand the folder structure
Decision Framework
After this experience, I created a decision framework for choosing between approaches.
Choose LangChain or Semantic Kernel When:
PRODUCTION REQUIREMENTS========================[ ] Multi-user system with authentication[ ] SLA requirements (99.9% uptime)[ ] Horizontal scaling needed[ ] Complex state across sessions
ORCHESTRATION COMPLEXITY========================[ ] Multi-step reasoning with branching[ ] Agent-to-agent communication[ ] Tool calling with retry logic[ ] Vector database memory systems
INTEGRATION REQUIREMENTS========================[ ] Multiple LLM providers (OpenAI, Anthropic, local)[ ] Enterprise integrations (databases, APIs, queues)[ ] Observability (tracing, logging, metrics)[ ] Rate limiting and cost management
TEAM STRUCTURE==============[ ] Multiple developers collaborating[ ] Need standardized patterns[ ] Code review and quality gates requiredIf you checked 3 or more boxes, consider a framework.
Choose File-Based When:
PERSONAL PRODUCTIVITY=====================[ ] Individual developer workflow[ ] Knowledge management system[ ] Personal automation scripts
RAPID PROTOTYPING=================[ ] Testing ideas quickly[ ] POC development[ ] Learning and experimentation
TRANSPARENCY REQUIREMENTS========================[ ] Non-technical stakeholders need to understand[ ] Straightforward debugging needed[ ] Simple version control critical
SIMPLE WORKFLOWS================[ ] Linear processing pipeline[ ] Single-model interactions[ ] Minimal state managementIf you checked 3 or more boxes, consider file-based.
Real World Example: When I Needed Both
I now use both approaches for different projects.
Personal Project: File-Based
My personal knowledge management system uses markdown files:
knowledge/├── projects/│ ├── project-a/│ │ ├── context.md # Project background│ │ ├── notes.md # Meeting notes│ │ └── decisions.md # Decision log│ └── project-b/│ └── ...├── templates/│ ├── daily-standup.md│ └── weekly-review.md└── agent.py # Simple 50-line scriptMy agent script:
from pathlib import Pathfrom anthropic import Anthropic
def generate_weekly_review(projects_dir: Path) -> str: client = Anthropic() contexts = []
for project_dir in projects_dir.iterdir(): if project_dir.is_dir(): context_file = project_dir / "context.md" notes_file = project_dir / "notes.md" if context_file.exists() and notes_file.exists(): contexts.append( f"PROJECT: {project_dir.name}\n" f"Context: {context_file.read_text()}\n" f"Notes: {notes_file.read_text()}" )
response = client.messages.create( model="claude-opus-4-20250514", max_tokens=2000, messages=[{ "role": "user", "content": f"Generate a weekly review:\n\n{'-'*40}\n".join(contexts) }] )
return response.content[0].textWhy file-based works here: I’m the only user, the workflow is linear, and I need to debug quickly.
Work Project: LangChain
At work, we built a customer support automation system:
Customer Support Agent├── Multi-tenant (each customer isolated)├── Multiple LLMs (GPT-4 for reasoning, Claude for empathy)├── Vector database (conversation history)├── Integration with:│ ├── Zendesk (ticket system)│ ├── Slack (notifications)│ ├── Salesforce (customer data)│ └── Internal APIs (product info)├── Observability (LangSmith tracing)└── Multiple developers working on same codebasefrom langchain.agents import AgentExecutorfrom langchain.memory import VectorStoreMemoryfrom langchain.tools import StructuredToolfrom langchain_openai import ChatOpenAIfrom langchain_anthropic import ChatAnthropicfrom pydantic import BaseModelimport logging
class SupportAgent: def __init__(self, customer_id: str): # Multi-model setup self.reasoning_llm = ChatOpenAI(model="gpt-4") self.empathy_llm = ChatAnthropic(model="claude-opus-4-20250514")
# Per-customer memory self.memory = VectorStoreMemory( customer_id=customer_id, vectorstore=self._init_vectorstore() )
# Enterprise integrations self.tools = [ self._create_zendesk_tool(), self._create_salesforce_tool(customer_id), self._create_product_api_tool() ]
self.agent = AgentExecutor( agent=self._create_agent(), tools=self.tools, memory=self.memory, callbacks=[self._init_tracing()], max_iterations=10 )
self.logger = logging.getLogger(f"support.{customer_id}")
async def handle_ticket(self, ticket_id: str) -> dict: """Process support ticket with full observability.""" try: result = await self.agent.ainvoke({ "input": f"Handle ticket {ticket_id}" }) self.logger.info(f"Ticket {ticket_id} resolved") return result except Exception as e: self.logger.error(f"Failed to handle ticket: {e}") # Fallback to human agent await self._escalate_to_human(ticket_id, str(e)) raiseWhy LangChain works here: Multiple users, complex integrations, observability requirements, and team collaboration.
Common Mistakes I Made
Mistake 1: Over-Engineering Personal Projects
I initially built my personal knowledge agent with:
- LangChain agent framework
- Redis for memory
- PostgreSQL for persistence
- Prometheus metrics
- Docker containerization
Result: 50+ lines of YAML configuration for a script I run once per week.
The file-based version: 50 lines of Python, no dependencies beyond the Anthropic SDK.
Mistake 2: Under-Engineering Production Systems
For a work project, I tried to use file-based agents because “it worked for my personal projects.” The result:
Issues encountered:- Race conditions when multiple users accessed same files- No audit trail for compliance- Memory leaks (context files grew unbounded)- No retry logic for failed API calls- No metrics for debugging production issues- Hard to onboard new developers (no patterns)We migrated to LangChain after two production incidents.
Mistake 3: Ignoring Team Capabilities
I once recommended LangChain to a team where:
- Only one developer knew Python
- No one had used async/await before
- No experience with vector databases
Result: Three months of learning curve before shipping a simple feature.
Comparison Summary
| Aspect | LangChain/Semantic Kernel | File-Based ||---------------------|------------------------------|-------------------------|| Learning curve | Weeks to months | Hours to days || Debugging | Through abstraction layers | Direct file inspection || Observability | Built-in (LangSmith, etc.) | Manual implementation || State management | Automatic | Manual || Multi-tenancy | Built-in patterns | Custom implementation || Team scalability | High (patterns/standards) | Low (ad-hoc) || Prototyping speed | Slower (setup overhead) | Faster (minimal setup) || Production ready | Yes (with configuration) | Needs custom work || Non-tech accessible | No | Yes (markdown files) |Related Knowledge
Why Frameworks Add Abstraction Layers
LangChain and Semantic Kernel operate at a high abstraction level to solve common problems:
- Model switching: Swap between OpenAI, Anthropic, local models with one line change
- Memory management: Automatic context window handling
- Tool orchestration: Retry logic, error handling, parallel execution
- Observability: Built-in tracing and logging
The cost of this abstraction is complexity. You trade direct control for convenience.
The Unix Philosophy Alternative
File-based approaches follow the Unix philosophy:
- Do one thing well
- Use text as universal interface
- Compose small tools
This philosophy excels when your problem fits the pattern: linear processing, text-based context, single user.
When to Migrate
Start simple, add complexity when needed:
Signals it's time to migrate from file-based to framework:===========================================================1. You're implementing your own memory management2. Multiple users need access to the same agent3. You've added more than 3 custom error handling patterns4. Debugging takes longer than development5. You're manually implementing retry logic6. You need metrics for production monitoring7. Team members are confused by your folder structureSummary
The choice between LangChain/Semantic Kernel and file-based approaches isn’t about one being better than the other. It’s about matching the tool to the job.
For personal projects with simple workflows: File-based approaches win. Markdown files are transparent, debuggable, and require minimal learning.
For production systems with complex orchestration: Frameworks like LangChain provide essential patterns for reliability, scaling, and team collaboration.
The best architecture serves your specific use case without unnecessary complexity. Start simple. Add abstraction only when the pain of simplicity exceeds the pain of complexity.
For my weekly summary agent, I went back to the file-based approach. It takes 3 seconds to understand, 10 seconds to debug, and does exactly what I need.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 LangChain Documentation
- 👨💻 Microsoft Semantic Kernel
- 👨💻 Reddit: AI Agents Discussion
- 👨💻 File-Based Agent Pattern
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments