-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Framework overhaul - object returns, JSX objects, streaming pat…
…tern, and context API (#36) Major overhaul of the framework based on my experience building out the first robust example (`HackerNewsAnalyzer`). This PR includes the following: - Support for object returns and nested JSX elements in objects - Adds a natural pattern for delineating between streaming and sync LLM output (`Component` vs `StreamComponent`) - A context API that can accumulate and track state across subgraphs (used to make streaming work without global state) - Add a robust `HackerNewsAnalyzer` example that summarizes posts from the front page (n=500) and runs sentiment analysis over all of the comments to product a report and a tweet in the style of Paul Graham. ## Object returns & nested JSX Add two features to streamline workflow authorship: 1. Support for returning objects instead of just Element Element[] and string 2. Add the ability to embed Elements inside of objects ### Direct object return - Components can now return plain objects directly instead of only JSX elements - Eliminates the need for wrapper components when combining multiple outputs - Example: `return { summary, commentAnalysis }` instead of needing to define another component that manipulates the output shape from tuple -> object ### Nested JSX Resolution in Objects - The execute function now recursively resolves JSX elements within returned objects - Enables natural composition where object properties can be JSX elements Example: ```tsx return { analyses: stories.map(story => ({ summary: <PostSummarizer story={story} />, commentAnalysis: <CommentsAnalyzer comments={story.comments} /> })) } ``` Together both of these changes eliminate the need for wrapper components, boilerplate, and reduces the awkwardness of having to deal with weakly typed tuples in fanout and parallel operations. ## Streaming Patterns LLM components need to support both (1) synchronous output -- the 99% use case -- and (2) streaming output -- the 1% output that is typically only used during the last step when output is being rendered to users via a UI. We want to optimize for sync output since this is the common use case. To support this we've added: 1. `gensx.StreamComponent`: a special kind of component that returns a standard formal with a plain text value and a stream to get the output 2. A standard `stream` prop available on all streaming components. This defaults to false, but can be set to true to trigger streaming for that component only. Further nested components revert back to sync invocation unless they specify `stream: true` as well. The final output can be streaming or not, depending on the types of the components that get returned and whether or not `stream` is set on those elements allowing you to mix/match if needed. This enables writing any component to either return a text value or a stream. Authors don't have to worry about supporting both formats. Consumers get the prompt value in the 99% use case or can set `stream: true` and get the expected behavior for the 1% use case. ## Context API A context API that can be used to accumulate and track state across different parts of the subgraph. Streaming is intrinsic to the framework, and the framework is very aware of the streaming context values. However, the general API should support other use cases in the future such as: - tracing - loggers - error boundaries - caching - configuration inheritence ## HN Example Adds a more robust real world example that analyzes the front page of HN: 1. pulls the top 500 pages, and filters down to text posts 2. summarizes the posts, analyzes comments for sentiment 4. feeds all of this into a report writer 5. rewrite the report in the voice of Paul Graham 6. writes a tweet about the post in the style of PG Powerful example in just 300 LOC! Co-authored-by: Jeremy Moseley <[email protected]>
- Loading branch information
Showing
21 changed files
with
1,549 additions
and
743 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
When you sift through the vibrant exchanges on Hacker News, you start to see patterns in the chaos. It's like peering into a crystal ball, albeit one that's a bit cracked and smudged with fingerprints. The topics buzzing around tell us a lot about where technology is heading and what’s gnawing at the minds of those in the trenches. | ||
|
||
### Exciting Waves in Tech | ||
|
||
1. **AI and Machine Learning:** | ||
AI is the new electricity, lighting up the imagination of everyone from garage tinkerers to corporate juggernauts. Take, for instance, the buzz around [Semantic Search for ArXiv Papers](https://news.ycombinator.com/item?id=42507116). It's a project that uses AI to sift through the academic haystack to find those elusive needles. The real excitement lies in AI's potential to revolutionize fields like healthcare by making diagnostics smarter, or code smarter by augmenting software development. | ||
|
||
2. **Cutting-Edge Development Tools:** | ||
Developers are always on the hunt for ways to do more with less hassle. Enter tools like [Cudair](https://news.ycombinator.com/item?id=42484994), which offers live-reloading for CUDA applications. Such innovations are like giving a chef a sharper knife—they can still chop faster without losing a finger. In high-performance computing, where every millisecond counts, these tools are nothing short of revolutionary. | ||
|
||
3. **Decentralization and Privacy:** | ||
Privacy is the new frontier, and decentralized systems are the pioneers staking their claim. Projects such as [TideCloak](https://news.ycombinator.com/item?id=42460131) are building the infrastructure for a world where control over personal data is not just a pipe dream. As our lives move increasingly online, the demand for privacy and user sovereignty is growing louder. | ||
|
||
### Storm Clouds on the Horizon | ||
|
||
1. **Tech Monopolies and Privacy Concerns:** | ||
The tech giants are feeling more and more like Big Brother to the Hacker News crowd. Consider the uproar over [uBlock in Chrome](https://news.ycombinator.com/item?id=42506506). Users are frustrated by changes that seem to prioritize profits over privacy, making them feel like pawns rather than participants. | ||
|
||
2. **AI-Induced Job Anxiety:** | ||
The specter of AI-induced job loss is haunting the discussions, especially concerning the [future of programming jobs](https://news.ycombinator.com/item?id=42500926). There's a palpable tension between the promise of AI augmenting human work and the fear of it replacing entry-level positions. Yet, the optimists argue that AI will create more jobs than it destroys, akin to how ATMs didn't eliminate bank tellers. | ||
|
||
3. **Cloud Dependency Issues:** | ||
The reliance on cloud services is a double-edged sword. While they offer convenience, they also introduce risks, as seen with issues like those surrounding [Google Authenticator](https://news.ycombinator.com/item?id=42510300). The fragility of these systems can be unnerving, especially when they hold the keys to your digital kingdom. | ||
|
||
### Surprising Currents | ||
|
||
1. **AI's Cross-Disciplinary Ventures:** | ||
AI is proving to be a versatile tool, popping up in unexpected places like [crossword generation](https://news.ycombinator.com/item?id=42496953). It's like discovering your hammer can also make a decent screwdriver. This cross-pollination of ideas is pushing AI into creative domains, sparking new forms of problem-solving. | ||
|
||
2. **DIY and Open-Source Renaissance:** | ||
There's a resurgence of interest in DIY and open-source projects, as demonstrated by [Musoq](https://news.ycombinator.com/item?id=42453650). It enables querying across diverse data sources with SQL, empowering developers to break free from the shackles of proprietary software. It's a reminder that innovation often thrives outside corporate walls. | ||
|
||
3. **Tech’s Cultural and Environmental Impact:** | ||
Projects like [Movie Iris](https://news.ycombinator.com/item?id=42462348) show a unique blend of art, tech, and environmental consciousness. By using color extraction for film analysis, they encourage us to ponder the deeper implications of our cultural consumption and its environmental footprint. | ||
|
||
### The Collective Pulse | ||
|
||
The atmosphere among the tech-savvy is one of cautious optimism. There's a tangible excitement about the transformative power of technology, tempered by a keen awareness of the ethical and societal challenges it brings. As we forge ahead, the focus remains on balancing innovation with responsibility, ensuring that the future we build is one we can be proud of. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
AI is shaking up fields you wouldn't expect, like crossword puzzles—proof that the real magic lies in AI's unexpected versatility, not just its raw power. #AI #Innovation #TechTrends |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,8 @@ | |
"name": "gensx", | ||
"version": "0.1.1", | ||
"description": "Make LLMs work good", | ||
"main": "dist/index.cjs", | ||
"module": "lib/index.js", | ||
"main": "dist/index.js", | ||
"module": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"author": "", | ||
"license": "MIT", | ||
|
@@ -20,19 +20,19 @@ | |
"packageManager": "[email protected]", | ||
"type": "module", | ||
"scripts": { | ||
"build:watch": "pnpm build:clean && pnpm generate-dist --watch", | ||
"build:watch": "pnpm build:clean && tsc -p tsconfig.prod.json --watch", | ||
"dev": "nodemon", | ||
"prepublishOnly": "pnpm i && pnpm build", | ||
"build": "pnpm validate-typescript && pnpm build:clean && pnpm generate-dist", | ||
"build": "pnpm validate-typescript && pnpm build:clean && pnpm build:dist", | ||
"test": "rimraf coverage && pnpm test:unit", | ||
"test:watch": "vitest", | ||
"test:unit": "vitest run --coverage", | ||
"lint": "eslint --ignore-path .gitignore . --ext .js,.ts", | ||
"lint:fix": "eslint --ignore-path .gitignore . --ext .js,.ts --fix", | ||
"lint:file": "eslint --ignore-path .gitignore", | ||
"validate-typescript": "tsc -p tsconfig.prod.json --noEmit", | ||
"generate-dist": "tsup src/index.ts --minify --tsconfig tsconfig.prod.json --dts --format cjs,esm --out-dir dist --entry.jsx-runtime=src/jsx-runtime.ts --entry.jsx-dev-runtime=src/jsx-dev-runtime.ts --entry.index=src/index.ts", | ||
"build:clean": "rimraf dist; exit 0", | ||
"build:clean": "rimraf dist", | ||
"build:dist": "tsc -p tsconfig.prod.json --outDir dist --declaration", | ||
"prepare": "[ -f .husky/install.mjs ] && node .husky/install.mjs || true" | ||
}, | ||
"devDependencies": { | ||
|
@@ -56,7 +56,6 @@ | |
"prettier": "^3.4.2", | ||
"rimraf": "^6.0.1", | ||
"tsconfig-paths": "^4.2.0", | ||
"tsup": "^8.3.5", | ||
"tsx": "^4.19.2", | ||
"typescript": "^5.7.2", | ||
"unplugin-swc": "^1.5.1", | ||
|
@@ -72,22 +71,19 @@ | |
], | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./dist/index.d.ts", | ||
"default": "./dist/index.js" | ||
}, | ||
"require": { | ||
"types": "./dist/index.d.cts", | ||
"default": "./dist/index.cjs" | ||
} | ||
"types": "./dist/index.d.ts", | ||
"default": "./dist/index.js" | ||
}, | ||
"./jsx-runtime": { | ||
"import": "./dist/jsx-runtime.js", | ||
"require": "./dist/jsx-runtime.cjs" | ||
"types": "./dist/jsx-runtime.d.ts", | ||
"default": "./dist/jsx-runtime.js" | ||
}, | ||
"./jsx-dev-runtime": { | ||
"import": "./dist/jsx-dev-runtime.js", | ||
"require": "./dist/jsx-dev-runtime.cjs" | ||
"types": "./dist/jsx-dev-runtime.d.ts", | ||
"default": "./dist/jsx-dev-runtime.js" | ||
} | ||
}, | ||
"dependencies": { | ||
"openai": "^4.77.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import * as gsx from "@/index"; | ||
|
||
interface LLMResearchBrainstormProps { | ||
prompt: string; | ||
} | ||
type LLMResearchBrainstormOutput = string[]; | ||
const LLMResearchBrainstorm = gsx.Component< | ||
LLMResearchBrainstormProps, | ||
LLMResearchBrainstormOutput | ||
>(async ({ prompt }) => { | ||
console.log("🔍 Starting research for:", prompt); | ||
const topics = await Promise.resolve(["topic 1", "topic 2", "topic 3"]); | ||
return topics; | ||
}); | ||
|
||
interface LLMResearchProps { | ||
topic: string; | ||
} | ||
type LLMResearchOutput = string; | ||
const LLMResearch = gsx.Component<LLMResearchProps, LLMResearchOutput>( | ||
async ({ topic }) => { | ||
console.log("📚 Researching topic:", topic); | ||
return await Promise.resolve(`research results for ${topic}`); | ||
}, | ||
); | ||
|
||
interface LLMWriterProps { | ||
research: string; | ||
prompt: string; | ||
} | ||
type LLMWriterOutput = string; | ||
const LLMWriter = gsx.Component<LLMWriterProps, LLMWriterOutput>( | ||
async ({ research, prompt }) => { | ||
console.log("✍️ Writing draft based on research"); | ||
return await Promise.resolve( | ||
`**draft\n${research}\n${prompt}\n**end draft`, | ||
); | ||
}, | ||
); | ||
|
||
interface LLMEditorProps { | ||
draft: string; | ||
} | ||
type LLMEditorOutput = string; | ||
const LLMEditor = gsx.Component<LLMEditorProps, LLMEditorOutput>( | ||
async ({ draft }) => { | ||
console.log("✨ Polishing final draft"); | ||
return await Promise.resolve(`edited result: ${draft}`); | ||
}, | ||
); | ||
|
||
interface WebResearcherProps { | ||
prompt: string; | ||
} | ||
type WebResearcherOutput = string[]; | ||
const WebResearcher = gsx.Component<WebResearcherProps, WebResearcherOutput>( | ||
async ({ prompt }) => { | ||
console.log("🌐 Researching web for:", prompt); | ||
const results = await Promise.resolve([ | ||
"web result 1", | ||
"web result 2", | ||
"web result 3", | ||
]); | ||
return results; | ||
}, | ||
); | ||
|
||
type ParallelResearchOutput = [string[], string[]]; | ||
interface ParallelResearchComponentProps { | ||
prompt: string; | ||
} | ||
const ParallelResearch = gsx.Component< | ||
ParallelResearchComponentProps, | ||
ParallelResearchOutput | ||
>(({ prompt }) => ( | ||
<> | ||
<LLMResearchBrainstorm prompt={prompt}> | ||
{topics => topics.map(topic => <LLMResearch topic={topic} />)} | ||
</LLMResearchBrainstorm> | ||
<WebResearcher prompt={prompt} /> | ||
</> | ||
)); | ||
|
||
interface BlogWritingWorkflowProps { | ||
prompt: string; | ||
} | ||
type BlogWritingWorkflowOutput = string; | ||
export const BlogWritingWorkflow = gsx.Component< | ||
BlogWritingWorkflowProps, | ||
BlogWritingWorkflowOutput | ||
>(async ({ prompt }) => ( | ||
<ParallelResearch prompt={prompt}> | ||
{([catalogResearch, webResearch]) => { | ||
console.log("🧠 Research:", { catalogResearch, webResearch }); | ||
return ( | ||
<LLMWriter | ||
research={[catalogResearch.join("\n"), webResearch.join("\n")].join( | ||
"\n\n", | ||
)} | ||
prompt={prompt} | ||
> | ||
{draft => <LLMEditor draft={draft} />} | ||
</LLMWriter> | ||
); | ||
}} | ||
</ParallelResearch> | ||
)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { gsx } from "@/index"; | ||
import { createLLMService } from "@/llm"; | ||
|
||
const llm = createLLMService({ | ||
model: "gpt-4", | ||
temperature: 0.7, | ||
}); | ||
|
||
interface ChatCompletionProps { | ||
prompt: string; | ||
} | ||
|
||
export const ChatCompletion = gsx.StreamComponent<ChatCompletionProps, string>( | ||
async ({ prompt }) => { | ||
// Use the LLM service's streaming API | ||
const result = await llm.completeStream(prompt); | ||
|
||
return { | ||
stream: () => result.stream(), | ||
value: result.value, | ||
}; | ||
}, | ||
); |
Oops, something went wrong.