diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..054ffc1 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,27 @@ +name: Build and Deploy Docs +on: + push: + branches: [ docs-build ] +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.x' + - run: pip install mkdocs mkdocs-material 'mkdocstrings[python]' mkdocs-gen-files + # Install packages so mkdocstrings can import them + - run: pip install -e sdk + - run: pip install -e instrumentation-packages/codon-instrumentation-langgraph + - run: mkdocs build + # Upload artifact for Pages + - uses: actions/upload-pages-artifact@v3 + with: + path: ./site + # Deploy from artifact + - uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py new file mode 100644 index 0000000..7fbc124 --- /dev/null +++ b/docs/gen_ref_pages.py @@ -0,0 +1,29 @@ +"""Generate API reference pages.""" +import mkdocs_gen_files + +# Generate content for api-reference.md +with mkdocs_gen_files.open("api-reference.md", "w") as f: + f.write("""# API Reference + +## Core SDK (`codon_sdk`) + +### Agents + +::: codon_sdk.agents.CodonWorkload + +::: codon_sdk.agents.ExecutionReport + +### Instrumentation Schemas + +::: codon_sdk.instrumentation.schemas.nodespec.NodeSpec + +::: codon_sdk.instrumentation.schemas.logic_id.LogicRequest + +## Instrumentation Packages + +### LangGraph Integration (`codon-instrumentation-langgraph`) + +::: codon.instrumentation.langgraph.initialize_telemetry + +::: codon.instrumentation.langgraph.LangGraphWorkloadAdapter +""") \ No newline at end of file diff --git a/docs/public/api-reference.md b/docs/public/api-reference.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/public/building-from-scratch.md b/docs/public/building-from-scratch.md new file mode 100644 index 0000000..2351ba7 --- /dev/null +++ b/docs/public/building-from-scratch.md @@ -0,0 +1,215 @@ +# Building from Scratch + +If you're building agents from the ground up, [CodonWorkload](api-reference.md#codon_sdk.agents.CodonWorkload) provides a flexible foundation for creating single-agent and multi-agent workflows with token-based execution and comprehensive audit trails. + +## The CodonWorkload Class + +CodonWorkload is the foundation for building observable AI agents. Whether you build from scratch or use frameworks like LangGraph, it gives your agents the ability to track their own performance, costs, and behavior over time. + +**Key capabilities:** +- **Node registration**: Add Python functions as workflow steps +- **Graph definition**: Connect nodes with directed edges to define execution flow +- **Token-based execution**: Messages flow between nodes with full provenance tracking +- **Audit trails**: Automatic recording of every step, decision, and data transformation +- **Runtime operations**: Nodes can emit messages, record custom events, and share state + +For complete method signatures and parameters, see the [API Reference](api-reference.md). + +## Key Concepts + +**Nodes**: Individual Python functions that perform specific tasks (e.g., "summarize", "validate", "format"). Each node receives input, processes it, and can emit output to other nodes. + +**Edges**: Directed connections between nodes that define how tokens (messages) flow through your workflow. + +**Tokens**: Immutable messages that carry data between nodes, each with unique provenance tracking. This creates an immutable tape of your agent's actions during invocation that can be used for compliance monitoring, audit, evaluation, debugging, and experimentation. + +**Runtime**: The execution context that provides nodes access to operations like `runtime.emit()`, `runtime.record_event()`, and `runtime.state`. + +## Single-Agent Workflow + +Here's a simple Q&A agent that processes a user question: + +```python +from codon_sdk.agents import CodonWorkload +from datetime import datetime + +# Define a workload +workload = CodonWorkload(name="QA-Agent", version="0.1.0") + +# Define node functions +def call_model(message: Dict[str, Any], *, runtime, context): + prompt = message["question"] + answer = call_openai(prompt, system="You are a helpful assistant that answers questions you are asked.") + runtime.record_event("call_model", metadata={"answer_length": len(answer)}) + runtime.emit("finalize", {"question": message["question"], "answer": answer}) + return answer + +def prompt_builder(message: Dict[str, Any], *, runtime, context): + question = message["question"] + prompt = ( + "Answer the user's question in clear, friendly prose." + f"Question: {question}" + ) + runtime.record_event("prompt_created", metadata={"length": len(prompt)}) + runtime.emit("call_model", {"question": question}) + return prompt + +def finalize(message: Dict[str, Any], *, runtime, context): + result = { + "question": message["question"], + "answer": message["answer"], + "timestamp": datetime.utcnow().isoformat() + "Z", + } + runtime.record_event("finalized", metadata={"question_hash": hash(message["question"])}) + return result + +# Add the nodes +workload.add_node(call_model, name="call_model", role="llm") +workload.add_node(finalize, name="finalize", role="postprocess") +workload.add_node(prompt_builder, name="prompt_builder", role="format_prompt") + +# Define the edges +workload.add_edge("prompt_builder", "call_model") +workload.add_edge("call_model", "finalize") + +# Execute the workload +report = workload.execute({"question": "What is the meaning of life, the universe, and everything?"}, deployment_id="local") + +# Check results +final_result = report.node_results("finalize")[-1] +print(f"Logic ID: {workload.logic_id}") +``` + +## Multi-Agent Workflow + +For more complex scenarios, you can orchestrate multiple agents that collaborate through token passing and shared state: + +```python +def build_multi_agent_workload() -> CodonWorkload: + workload = CodonWorkload(name="Research-Writer", version="0.1.0") + + def planner(message: Dict[str, Any], *, runtime, context): + topic = message["topic"] + prompt = ( + "Design a concise research plan outlining key angles and questions." + f"Topic: {topic}" + "Return a numbered list with three focus areas." + ) + plan = call_openai(prompt, system="You are a strategic project planner.") + runtime.state["plan"] = plan + runtime.emit("researcher", {"topic": topic, "plan": plan}) + return plan + + def researcher(message: Dict[str, Any], *, runtime, context): + prompt = ( + "Given this plan, provide bullet insights (max 3 per focus area)." + f"Plan: {message['plan']}" + ) + insights = call_openai(prompt, system="You are an expert analyst.") + runtime.state["insights"] = insights + runtime.emit( + "writer", + { + "topic": message["topic"], + "plan": message["plan"], + "insights": insights, + }, + ) + return insights + + def writer(message: Dict[str, Any], *, runtime, context): + prompt = ( + "Write a concise executive summary (<=120 words) in a warm, professional tone." + f"Topic: {message['topic']}" + f"Plan: {message['plan']}" + f"Insights: {message['insights']}" + ) + summary = call_openai(prompt, system="You are a skilled report writer.") + runtime.record_event("summary_created", metadata={"length": len(summary)}) + return { + "topic": message["topic"], + "plan": message["plan"], + "insights": message["insights"], + "summary": summary, + } + + workload.add_node(planner, name="planner", role="planner") + workload.add_node(researcher, name="researcher", role="analyst") + workload.add_node(writer, name="writer", role="author") + workload.add_edge("planner", "researcher") + workload.add_edge("researcher", "writer") + + return workload + +# Use the multi-agent workload +multi_agent = build_multi_agent_workload() +project = {"topic": "The impact of community gardens on urban wellbeing"} +multi_report = multi_agent.execute(project, deployment_id="demo", max_steps=20) +final_document = multi_report.node_results("writer")[-1] +``` + +## Key Concepts + +### Token-Based Execution +- Each node receives a token `message` with data from previous nodes +- Nodes can emit new tokens to downstream nodes via `runtime.emit(target_node, payload)` +- Tokens carry immutable provenance with unique IDs, lineage, and timestamps + +### Runtime Operations +The `runtime` parameter provides access to workflow operations: +- `runtime.emit(node_name, payload)` - Send tokens to other nodes +- `runtime.record_event(event_type, metadata={})` - Add custom audit entries +- `runtime.state` - Shared dictionary for coordination between nodes +- `runtime.stop()` - Halt execution early + +### Audit Ledger +Every workflow execution generates a comprehensive audit trail: +- Token enqueue/dequeue events +- Node completion events +- Custom events via `runtime.record_event()` +- Execution context (deployment_id, logic_id, run_id) + +**Note:** Only fired nodes are represented in a workload run, so the complete workload definition may not be present in the workload run summary. Your audit trail shows actual execution paths, not all possible paths defined in your workflow. + +Access the audit ledger through the execution report: +```python +for event in report.ledger: + print(f"{event.timestamp.isoformat()} | {event.event_type} | {event.source_node}") +``` + +### Logic IDs +Each workload gets a deterministic Logic ID based on: +- Agent class metadata (name, version, description) +- Node definitions and their roles +- Graph topology (edges between nodes) + +Same workload structure = same Logic ID, enabling deduplication and version tracking. + +**Example: Logic ID Generation and Changes** +```python +# Based on test_codon_workload.py test patterns +workload = CodonWorkload(name="TestWorkload", version="0.1.0") + +def simple_node(message, *, runtime, context): + return f"Result: {message['input']}" + +workload.add_node(simple_node, name="test_node", role="processor") + +print(f"Initial Logic ID: {workload.logic_id}") +baseline_logic_id = workload.logic_id + +# Adding a node changes the Logic ID +def echo_node(message, *, runtime, context): + return message + +workload.add_node(echo_node, name="echo", role="responder") +workload.add_edge("test_node", "echo") + +print(f"After adding node: {workload.logic_id}") +print(f"Logic ID changed? {workload.logic_id != baseline_logic_id}") # True +``` + +This deterministic identification enables: +- **Deduplication**: Skip redundant executions of the same logic +- **Version tracking**: Compare agent iterations across deployments +- **Caching**: Store and retrieve results based on stable identifiers \ No newline at end of file diff --git a/docs/public/getting-started.md b/docs/public/getting-started.md new file mode 100644 index 0000000..6428c80 --- /dev/null +++ b/docs/public/getting-started.md @@ -0,0 +1,62 @@ +# Getting Started + +## Prerequisites +- Python 3.8 or newer +- `pip` and a virtual environment tool such as `venv` or `pipenv` +- Access to an OTLP-compatible collector if you plan to export telemetry + +## Installation +Clone the repository and install the core SDK in editable mode: + +```bash +python -m venv .venv +source .venv/bin/activate +pip install --upgrade pip +pip install -e sdk +``` + +Instrumentation packages are published independently. To work on one locally, install it the same way: + +```bash +pip install -e instrumentation-packages/codon-instrumentation-langgraph +``` + +> **Note:** The OpenAI package is currently a stub and will be populated in a future iteration. + +## Environment Configuration +| Variable | Purpose | +| -------- | ------- | +| `ORG_NAMESPACE` | Required by `NodeSpec` and instrumentation to scope identifiers. | +| `OTEL_SERVICE_NAME` | Optional service name applied during telemetry initialization. | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | Configure the OTLP collector when using the default exporter. | + +Set `ORG_NAMESPACE` before constructing `NodeSpec` objects or instrumented decorators will raise a validation error. + +You can set these environment variables directly: + +```bash +export ORG_NAMESPACE=your-org-name +export OTEL_SERVICE_NAME=your-service-name # optional +export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 # optional +``` + +Or create a `.env` file in your project root: + +```bash +# Required +ORG_NAMESPACE=your-org-name + +# Optional - only needed if using telemetry +OTEL_SERVICE_NAME=your-service-name +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 +``` + +## Next Steps + +Now that you have the SDK installed and configured, you can: + +- **Build from scratch**: Create custom agents with [CodonWorkload](building-from-scratch.md) +- **Use existing frameworks**: Integrate with [LangGraph](instrumentation/langgraph.md) or other supported frameworks +- **Learn the APIs**: Explore detailed documentation in the [API Reference](api-reference.md) + +For detailed information about NodeSpec and Logic ID generation, see the [API Reference](api-reference.md). \ No newline at end of file diff --git a/docs/public/index.md b/docs/public/index.md new file mode 100644 index 0000000..d495b90 --- /dev/null +++ b/docs/public/index.md @@ -0,0 +1,17 @@ +# Codon SDK Documentation + +Complete guide to building observable AI agents with the Codon SDK. + +## Documentation Sections + +- **[What is the Codon SDK](what-is-codon.md)** - Design philosophy and core concepts +- **[Getting Started](getting-started.md)** - Installation and basic setup +- **[Building from Scratch](building-from-scratch.md)** - Create agents with CodonWorkload +- **[Instrumentation](instrumentation/index.md)** - Framework-specific integrations (LangGraph, OpenAI, etc.) +- **[API Reference](api-reference.md)** - Complete API documentation + +## Quick Start + +1. **[Getting Started](getting-started.md)** - Installation and environment setup +2. **New to agent frameworks**: Create agents with [Building from Scratch](building-from-scratch.md) +3. **LangGraph users**: Integrate existing code with [LangGraph Integration](instrumentation/langgraph.md) \ No newline at end of file diff --git a/docs/public/instrumentation/index.md b/docs/public/instrumentation/index.md new file mode 100644 index 0000000..9501cad --- /dev/null +++ b/docs/public/instrumentation/index.md @@ -0,0 +1,31 @@ +# Instrumentation + +Framework-specific instrumentation packages that emit OpenTelemetry spans enriched with Codon metadata. + +## Available Integrations + +### **[LangGraph Integration](langgraph.md)** +Seamlessly integrate your existing LangGraph StateGraphs with Codon telemetry and observability. + +- Automatic node instrumentation via `LangGraphWorkloadAdapter` +- OpenTelemetry span export with Codon metadata +- Just a few lines of code integration + +## Coming Soon + +- **OpenAI Integration** - Direct instrumentation for OpenAI API calls +- **CrewAI Integration** - Support for CrewAI agent frameworks + +## Installation + +Instrumentation packages are installed separately from the core SDK: + +```bash +# Install core SDK +pip install -e sdk + +# Install specific instrumentation packages as needed +pip install -e instrumentation-packages/codon-instrumentation-langgraph +``` + +Each integration provides telemetry and observability for its respective framework while working alongside the core Codon SDK. \ No newline at end of file diff --git a/docs/public/instrumentation/langgraph.md b/docs/public/instrumentation/langgraph.md new file mode 100644 index 0000000..387d90f --- /dev/null +++ b/docs/public/instrumentation/langgraph.md @@ -0,0 +1,56 @@ +# For LangGraph Users + +If you're already using LangGraph, the Codon SDK provides seamless integration through the `LangGraphWorkloadAdapter`. This allows you to wrap your existing StateGraphs with minimal code changes while gaining comprehensive telemetry and observability. + +## Understanding State Graph vs Compiled Graph + +LangGraph has two distinct graph representations: +- **State Graph**: The graph you define and add nodes to during development +- **Compiled Graph**: The executable version created when you want to run the graph + +The `LangGraphWorkloadAdapter` works by wrapping your StateGraph and compiling it for you, allowing you to pass compile keyword arguments for features like checkpointers and long-term memory. + +## Using LangGraphWorkloadAdapter + +The primary way to integrate LangGraph with Codon is through the `LangGraphWorkloadAdapter.from_langgraph()` method: + +```python +from langgraph.graph import StateGraph +from langgraph.checkpoint.memory import MemorySaver +from codon.instrumentation.langgraph import LangGraphWorkloadAdapter + +# Your existing StateGraph +db_agent_graph = StateGraph(SQLAnalysisState) +db_agent_graph.add_node("query_resolver_node", self.query_resolver_node) +db_agent_graph.add_node("query_executor_node", self.query_executor_node) +# ... add more nodes and edges + +# Wrap with Codon adapter +self._graph = LangGraphWorkloadAdapter.from_langgraph( + db_agent_graph, + name="LangGraphSQLAgentDemo", + version="0.1.0", + description="A SQL agent created using the LangGraph framework", + tags=["langgraph", "demo", "sql"], + compile_kwargs={"checkpointer": MemorySaver()} +) +``` + +### Automatic Node Inference + +The adapter automatically infers nodes from your StateGraph, eliminating the need to manually instrument each node with decorators. This provides comprehensive telemetry out of the box. + +**Note:** Only fired nodes are represented in a workload run, so the complete workload definition may not be present in the workload run summary. This is particularly relevant for LangGraph workflows with conditional edges and branching logic—your execution reports will show actual paths taken, not all possible paths. + +### Compile Keyword Arguments + +You can pass any LangGraph compile arguments through `compile_kwargs`: +- Checkpointers for persistence +- Memory configurations +- Custom compilation options + +## Best Practices + +1. **Use the adapter**: `LangGraphWorkloadAdapter.from_langgraph()` provides comprehensive instrumentation with just a few lines of code +2. **Initialize telemetry early**: Call `initialize_telemetry()` before creating your workloads +3. **Leverage compile_kwargs**: Pass checkpointers and memory configurations through the adapter \ No newline at end of file diff --git a/docs/public/what-is-codon.md b/docs/public/what-is-codon.md new file mode 100644 index 0000000..88a81c6 --- /dev/null +++ b/docs/public/what-is-codon.md @@ -0,0 +1,100 @@ +# What is the Codon SDK? + +## Why Codon SDK? + +There is currently no way to identify AI agents that persists between invocations or across deployment environments. Every time you deploy an AI agent, it gets a new identity. There's no way to track that your 'customer-support-bot-v2' in staging is the same logic as production, making cost analysis, performance tracking, and debugging nearly impossible. + +This gap means AI agent teams struggle to measure their runtime invocations, track usage metrics at the node-level, perform lineage reconstruction, enable cost attribution, and conduct debugging & reliability analytics across environments. + +## What It Does + +That's why Codon assigns Universal IDs to AI agents, intelligently encoding their logic through Node IDs and Logic IDs that persist across deployments. The SDK wraps your existing framework code with [comprehensive observability](instrumentation/index.md), creating an immutable tape of agent actions that can be used for compliance monitoring, debugging, and cost optimization. + +The SDK provides common building blocks including immutable node specifications, logic ID generation, and a shared telemetry vocabulary. [Framework-specific instrumentation packages](instrumentation/index.md) emit OpenTelemetry spans enriched with Codon metadata. + +## How Codon Works: Two-Layer Architecture + +**Layer 1: The Core SDK** - Generates stable Node IDs and Logic IDs from agent graphs, plus provides the Workload interface that establishes the contract all concrete implementations must follow to communicate with the data collection system. + +**Layer 2: The Instrumentation Libraries** - Framework-specific packages that leverage the Core SDK to "wrap" third-party frameworks with telemetry sensors. Our goal is to "meet developers where they are" by making it simple to bring your existing framework and still get the visibility and insights that Codon can offer. + +## Core Capabilities + +**Audit-First Architecture** +- Every agent run generates a complete, replayable audit trail +- Immutable provenance tracking for every decision and data transformation +- Built for environments where you need to explain AI behavior + +**Framework-Agnostic Observability** +- Instrument existing [LangGraph](instrumentation/langgraph.md), OpenAI, and other framework code with minimal changes +- Standardized OpenTelemetry output works with any monitoring stack +- [Pluggable instrumentation packages](instrumentation/index.md) adapt to your current tools + +**Deterministic Identity System** +- Stable SHA-256 identifiers for functions, workflows, and deployments +- Enables reliable caching, deduplication, and version tracking +- Same logic produces same identifiers across environments + +**Production-Ready Execution** +- Portable logic that runs consistently from development to production +- Flexible graph patterns supporting cycles, feedback loops, and streaming +- Separation of agent logic from deployment-specific configurations + +[Get started with the core concepts →](getting-started.md) + +## Design Philosophy + +Codon Workload is the heart of our emerging agentic framework. The principles that guide its design: + +### Guiding Principles + +1. **Portability Over Infrastructure Lock-In** + Workloads must remain portable across environments. The same logic graph should run locally, in CI, or in production without re-authoring code. Runtime concerns (telemetry, persistence, scaling) should be injectable, not baked into agents. + +2. **Audit First** + Every agent run should be replayable. Provenance—who emitted a token, when, with which payload—needs to be preserved. This is not just a compliance checkbox; it is foundational for trust, debugging, and postmortems. + +3. **Composable Runtime** + The framework provides a default execution engine, but its components (token queues, state store, audit sink) are intended to be swappable. Teams should be able to plug in bespoke back-ends without rewriting agent logic. + +4. **Graph Flexibility** + Agents are not restricted to DAGs. Feedback loops, streaming, and reactive behaviours are first-class. The runtime must therefore handle cycles and long-lived flows gracefully. + +5. **Low Cognitive Load for Developers** + Authoring an agent should feel familiar: define functions, register them as nodes, wire edges. The runtime handles orchestration, tokens, and logging behind the scenes. + +## What We Have Today + +### Token-Based Execution +- Each node receives a token `message`, operates on it, and can emit new tokens to downstream nodes via `runtime.emit(...)`. +- Tokens carry immutable provenance: unique ID, lineage, parent link, timestamps, and origin node. +- Shared per-run state (`runtime.state`) allows nodes to coordinate while keeping logic in regular Python functions. + +### Auditable Ledger +- Every enqueue, dequeue, node completion, custom event, and stop request is recorded as an `AuditEvent`. +- `ExecutionReport` bundles node results, the audit ledger, runtime context (`deployment_id`, `logic_id`, `run_id`), and helper methods for quick inspection. +- Developers can insert their own audit entries using `runtime.record_event(...)` to add business-specific metadata. + +### Compliance-Focused Errors and Guardrails +- `WorkloadRegistrationError` highlights graph definition issues (duplicate nodes, missing edges). +- `WorkloadRuntimeError` covers invalid routing (emitting to unregistered nodes), step limit breaches, and unexpected node failures. +- `max_steps` can cap execution cycles to prevent runaway loops. + +## Why This Matters + +**For Development Teams:** +- **Faster debugging**: Complete visibility into multi-step agent workflows eliminates guesswork +- **Framework flexibility**: Keep using [LangGraph](instrumentation/langgraph.md), OpenAI, or other tools you know—just add observability +- **Reliable deployments**: Same agent logic runs consistently across development, staging, and production + +**For Operations Teams:** +- **Production confidence**: Comprehensive monitoring and alerting for AI agent behavior +- **Audit compliance**: Built-in provenance tracking meets regulatory requirements +- **Infrastructure independence**: Standard OpenTelemetry output works with your existing monitoring stack + +[Explore detailed API documentation →](api-reference.md) + +**For Organizations:** +- **Risk mitigation**: Full transparency into AI decision-making processes +- **Operational excellence**: Treat AI agents with the same rigor as traditional software systems +- **Strategic flexibility**: Evolve your AI stack without being locked into specific vendors or frameworks \ No newline at end of file diff --git a/instrumentation-packages/codon-instrumentation-langgraph/codon/instrumentation/langgraph/__init__.py b/instrumentation-packages/codon-instrumentation-langgraph/codon/instrumentation/langgraph/__init__.py index c420cf1..5dd155c 100644 --- a/instrumentation-packages/codon-instrumentation-langgraph/codon/instrumentation/langgraph/__init__.py +++ b/instrumentation-packages/codon-instrumentation-langgraph/codon/instrumentation/langgraph/__init__.py @@ -65,6 +65,18 @@ def from_langgraph( def initialize_telemetry(service_name: str = SERVICE_NAME or "") -> None: + """Initialize OpenTelemetry tracing with OTLP exporter. + + Sets up a TracerProvider with BatchSpanProcessor and OTLPSpanExporter to + export spans to an OTLP collector endpoint. + + Args: + service_name: Name for the service in telemetry data. Defaults to + OTEL_SERVICE_NAME environment variable or empty string. + + Example: + >>> initialize_telemetry(service_name="codon-langgraph-demo") + """ # Set up service name resource = Resource(attributes={"service.name": service_name}) # Set up TracerProvider & Exporter @@ -322,6 +334,32 @@ def track_node( introspection_target: Optional[Callable[..., Any]] = None, nodespec_kwargs: Optional[Mapping[str, Any]] = None, ): + """Decorator to add telemetry instrumentation to a function. + + When the decorated function executes, this decorator: + - Materializes a NodeSpec and captures its ID, signature, and schemas + - Wraps execution in an OpenTelemetry span (async and sync supported) + - Records inputs, outputs, and wall-clock latency via standardized span attributes + + Args: + node_name: Unique identifier for this node. + role: The node's role in the workflow. + model_name: Optional model identifier if this node uses an AI model. + model_version: Optional model version if this node uses an AI model. + introspection_target: Optional callable to introspect instead of decorated function. + nodespec_kwargs: Optional additional kwargs passed to NodeSpec constructor. + + Returns: + The decorated function with telemetry instrumentation added. + + Example: + >>> @track_node("retrieve_docs", role="retriever") + ... def retrieve_docs(query: str) -> List[str]: + ... return ["doc1", "doc2"] + + TODO: Document what 'role' parameter specifically represents + TODO: Clarify introspection_target use case and when to use it + """ def decorator(func): spec_callable = introspection_target or func spec_kwargs = dict(nodespec_kwargs or {}) diff --git a/instrumentation-packages/codon-instrumentation-langgraph/pyproject.toml b/instrumentation-packages/codon-instrumentation-langgraph/pyproject.toml index 2d7f50e..3f7ec07 100644 --- a/instrumentation-packages/codon-instrumentation-langgraph/pyproject.toml +++ b/instrumentation-packages/codon-instrumentation-langgraph/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ "Operating System :: OS Independent", ] dependencies = [ - "codon_sdk @ git+https://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@github.com/Codon-Ops/codon-sdk.git#subdirectory=sdk", + "codon_sdk", "opentelemetry-api", "opentelemetry-sdk", "opentelemetry-exporter-otlp-proto-grpc", diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..2a9c8d0 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,35 @@ +site_name: Codon SDK Documentation +site_description: Observable AI agents with the Codon SDK + +theme: + name: material + features: + - navigation.sections + - toc.integrate + - search.suggest + - content.code.copy + +plugins: + - search + - gen-files: + scripts: + - docs/gen_ref_pages.py + - mkdocstrings: + handlers: + python: + options: + docstring_style: google + show_root_heading: true + show_root_full_path: false + show_signature_annotations: true + +docs_dir: docs/public +nav: + - Home: index.md + - What is Codon: what-is-codon.md + - Getting Started: getting-started.md + - Building from Scratch: building-from-scratch.md + - Instrumentation: + - Overview: instrumentation/index.md + - LangGraph: instrumentation/langgraph.md + - API Reference: api-reference.md \ No newline at end of file diff --git a/sdk/src/codon_sdk/agents/codon_workload.py b/sdk/src/codon_sdk/agents/codon_workload.py index 68522df..2965a85 100644 --- a/sdk/src/codon_sdk/agents/codon_workload.py +++ b/sdk/src/codon_sdk/agents/codon_workload.py @@ -269,6 +269,14 @@ def __init__( description: Optional[str] = None, tags: Optional[Sequence[str]] = None, ) -> None: + """Initialize workload with metadata. + + Args: + name: Unique identifier for the workload type. + version: Semantic version (e.g., "1.2.0"). + description: Human-readable purpose description. + tags: Categorization tags. + """ self._node_specs: Dict[str, NodeSpec] = {} self._node_functions: Dict[str, Callable[..., Any]] = {} self._edges: Set[Tuple[str, str]] = set() @@ -327,6 +335,22 @@ def add_node( model_name: Optional[str] = None, model_version: Optional[str] = None, ) -> NodeSpec: + """Add a node (computational step) to the workload. + + Args: + function: The Python function to execute for this node. + name: Unique identifier for this node within the workload. + role: The node's role in the workflow (e.g., "processor", "validator"). + org_namespace: Organization namespace for scoping. Defaults to ORG_NAMESPACE env var. + model_name: Optional model identifier if this node uses an AI model. + model_version: Optional model version if this node uses an AI model. + + Returns: + The generated NodeSpec with a unique ID. + + Raises: + WorkloadRegistrationError: If a node with this name already exists. + """ if name in self._node_specs: raise WorkloadRegistrationError(f"Node '{name}' already registered") @@ -347,6 +371,18 @@ def add_node( return nodespec def add_edge(self, source_name: str, destination_name: str) -> None: + """Add an edge between two nodes in the workload. + + Args: + source_name: Name of the source node. + destination_name: Name of the destination node. + + Raises: + WorkloadRegistrationError: If either node name doesn't exist. + + TODO: Clarify what source_name and destination_name semantically represent + TODO: Document whether this creates directed edges and how token flow works + """ if source_name not in self._node_specs: raise WorkloadRegistrationError(f"Unknown source node '{source_name}'") if destination_name not in self._node_specs: diff --git a/sdk/src/codon_sdk/instrumentation/schemas/nodespec/__init__.py b/sdk/src/codon_sdk/instrumentation/schemas/nodespec/__init__.py index 9472649..20f5d7b 100644 --- a/sdk/src/codon_sdk/instrumentation/schemas/nodespec/__init__.py +++ b/sdk/src/codon_sdk/instrumentation/schemas/nodespec/__init__.py @@ -39,6 +39,14 @@ class NodeSpecEnv(BaseModel): class NodeSpec(BaseModel): + """Immutable specification that introspects Python callables and generates stable SHA-256 identifiers. + + NodeSpec inspects Python callables to capture the function signature, type hints, and optional + model metadata. It emits a deterministic SHA-256 ID that downstream systems can rely on. + + NodeSpec requires type annotations to build JSON schemas for inputs and outputs. If annotations + are missing, the generated schemas may be empty. + """ model_config = ConfigDict(extra="forbid", frozen=True) id: str = Field( default=None, description="The NodeSpec ID generated from the NodeSpec." @@ -81,6 +89,31 @@ def __init__( model_version: Optional[str] = None, **kwargs, ): + """Create a NodeSpec by introspecting a Python callable. + + Args: + name: The name of the node. + role: The role of the node. + callable: The Python function to introspect. + org_namespace: The namespace of the calling organization. Defaults to ORG_NAMESPACE env var. + model_name: The name of the model used in the node. + model_version: The version of the model currently used. + **kwargs: Additional fields for the NodeSpec. + + Raises: + NodeSpecValidationError: If ORG_NAMESPACE environment variable not set. + + Example: + >>> nodespec = NodeSpec( + ... org_namespace="acme", + ... name="summarize", + ... role="processor", + ... callable=summarize_function, + ... model_name="gpt-4o", + ... model_version="2024-05-13" + ... ) + >>> print(nodespec.id) + """ callable_attrs = analyze_function(callable) namespace = org_namespace or os.getenv(nodespec_env.OrgNamespace)